From eb30334a44695444c548baa9185c4e334aae7e64 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aur=C3=A9lien=20COUDERC?= Date: Tue, 3 Dec 2024 16:37:29 +0100 Subject: [PATCH] Import kdeplasma-addons_6.2.4.orig.tar.xz [dgit import orig kdeplasma-addons_6.2.4.orig.tar.xz] --- .git-blame-ignore-revs | 7 + .gitignore | 31 + .gitlab-ci.yml | 8 + .kde-ci.yml | 47 + CMakeLists.txt | 151 + LICENSES/BSD-3-Clause.txt | 26 + LICENSES/CC0-1.0.txt | 121 + LICENSES/GPL-2.0-only.txt | 319 ++ LICENSES/GPL-2.0-or-later.txt | 319 ++ LICENSES/GPL-3.0-only.txt | 625 ++++ LICENSES/GPL-3.0-or-later.txt | 625 ++++ LICENSES/LGPL-2.0-only.txt | 446 +++ LICENSES/LGPL-2.0-or-later.txt | 446 +++ LICENSES/LGPL-2.1-only.txt | 467 +++ LICENSES/LGPL-2.1-or-later.txt | 468 +++ LICENSES/LGPL-3.0-only.txt | 163 + LICENSES/LicenseRef-KDE-Accepted-GPL.txt | 12 + LICENSES/LicenseRef-KDE-Accepted-LGPL.txt | 12 + LICENSES/MIT.txt | 9 + Mainpage.dox | 7 + README.md | 60 + appiumtests/CMakeLists.txt | 36 + appiumtests/calculatortest.py | 97 + appiumtests/calendarplugins/CMakeLists.txt | 7 + .../calendarplugins/alternatecalendartest.py | 88 + appiumtests/keyboardindicatortest.py | 59 + applets/CMakeLists.txt | 21 + applets/binary-clock/Messages.sh | 2 + .../package/contents/config/config.qml | 21 + .../package/contents/config/main.xml | 34 + .../package/contents/ui/BinaryClock.qml | 93 + .../package/contents/ui/configGeneral.qml | 68 + .../binary-clock/package/contents/ui/main.qml | 67 + applets/binary-clock/package/metadata.json | 243 ++ applets/calculator/Messages.sh | 2 + .../calculator/package/contents/ui/main.qml | 586 ++++ applets/calculator/package/metadata.json | 146 + applets/colorpicker/CMakeLists.txt | 10 + applets/colorpicker/Messages.sh | 3 + .../package/contents/config/config.qml | 15 + .../package/contents/config/main.xml | 26 + .../package/contents/ui/ColorCircle.qml | 109 + .../package/contents/ui/ColorContextMenu.qml | 36 + .../contents/ui/CompactRepresentation.qml | 128 + .../package/contents/ui/ImageColors.qml | 23 + .../package/contents/ui/LoadingIndicator.qml | 53 + .../package/contents/ui/configGeneral.qml | 63 + .../colorpicker/package/contents/ui/logic.js | 100 + .../colorpicker/package/contents/ui/main.qml | 408 +++ applets/colorpicker/package/metadata.json | 149 + .../colorpicker/plugin/colorpickerplugin.cpp | 39 + applets/colorpicker/plugin/grabwidget.cpp | 79 + applets/colorpicker/plugin/grabwidget.h | 35 + applets/comic/CMakeLists.txt | 32 + applets/comic/Messages.sh | 3 + applets/comic/activecomicmodel.cpp | 54 + applets/comic/activecomicmodel.h | 42 + applets/comic/checknewstrips.cpp | 65 + applets/comic/checknewstrips.h | 44 + applets/comic/comic.cpp | 741 ++++ applets/comic/comic.h | 211 ++ applets/comic/comic.knsrc | 61 + applets/comic/comicdata.cpp | 104 + applets/comic/comicdata.h | 239 ++ applets/comic/comicinfo.cpp | 100 + applets/comic/comicinfo.h | 47 + applets/comic/comicmodel.cpp | 83 + applets/comic/comicmodel.h | 39 + applets/comic/comicsaver.cpp | 39 + applets/comic/comicsaver.h | 35 + applets/comic/engine/CMakeLists.txt | 31 + applets/comic/engine/cachedprovider.cpp | 231 ++ applets/comic/engine/cachedprovider.h | 152 + applets/comic/engine/comic.cpp | 248 ++ applets/comic/engine/comic.h | 63 + applets/comic/engine/comic_package.cpp | 43 + applets/comic/engine/comicprovider.cpp | 319 ++ applets/comic/engine/comicprovider.h | 264 ++ applets/comic/engine/comicproviderkross.cpp | 107 + applets/comic/engine/comicproviderkross.h | 50 + applets/comic/engine/comicproviderwrapper.cpp | 848 +++++ applets/comic/engine/comicproviderwrapper.h | 263 ++ .../engine/plasma-packagestructure-comic.json | 5 + applets/comic/engine/types.h | 63 + .../comic/package/contents/config/config.qml | 27 + .../comic/package/contents/ui/ButtonBar.qml | 65 + .../package/contents/ui/ComicBottomInfo.qml | 108 + .../package/contents/ui/ComicCentralView.qml | 135 + .../package/contents/ui/FullViewWidget.qml | 79 + .../comic/package/contents/ui/ImageWidget.qml | 65 + .../package/contents/ui/configAdvanced.qml | 55 + .../package/contents/ui/configAppearance.qml | 83 + .../package/contents/ui/configGeneral.qml | 121 + applets/comic/package/contents/ui/main.qml | 214 ++ applets/comic/package/metadata.json | 146 + applets/comic/stripselector.cpp | 167 + applets/comic/stripselector.h | 59 + applets/comic/stripselector_p.h | 49 + applets/dict/CMakeLists.txt | 36 + applets/dict/Messages.sh | 2 + .../dict/package/contents/config/config.qml | 17 + applets/dict/package/contents/config/main.xml | 13 + .../contents/ui/AvailableDictSheet.qml | 77 + .../contents/ui/ConfigDictionaries.qml | 105 + .../package/contents/ui/DictItemDelegate.qml | 66 + applets/dict/package/contents/ui/main.qml | 159 + applets/dict/package/metadata.json | 149 + applets/dict/plugin/dict_object.cpp | 100 + applets/dict/plugin/dict_object.h | 73 + applets/dict/plugin/dict_plugin.cpp | 36 + applets/dict/plugin/dictionariesmodel.cpp | 314 ++ applets/dict/plugin/dictionariesmodel.h | 118 + .../dict/sc-apps-accessories-dictionary.svgz | Bin 0 -> 15551 bytes applets/diskquota/CMakeLists.txt | 23 + applets/diskquota/Messages.sh | 2 + .../package/contents/ui/ListDelegateItem.qml | 88 + .../diskquota/package/contents/ui/main.qml | 105 + applets/diskquota/package/metadata.json | 147 + applets/diskquota/plugin/DiskQuota.cpp | 272 ++ applets/diskquota/plugin/DiskQuota.h | 109 + applets/diskquota/plugin/QuotaItem.cpp | 94 + applets/diskquota/plugin/QuotaItem.h | 52 + applets/diskquota/plugin/QuotaListModel.cpp | 183 + applets/diskquota/plugin/QuotaListModel.h | 71 + applets/diskquota/plugin/plugin.cpp | 25 + applets/fifteenPuzzle/CMakeLists.txt | 14 + applets/fifteenPuzzle/Messages.sh | 4 + .../package/contents/config/config.qml | 17 + .../package/contents/config/main.xml | 29 + .../package/contents/ui/FifteenPuzzle.qml | 374 ++ .../package/contents/ui/Piece.qml | 117 + .../package/contents/ui/blanksquare.svg | 125 + .../package/contents/ui/configAppearance.qml | 140 + .../package/contents/ui/main.qml | 30 + applets/fifteenPuzzle/package/metadata.json | 146 + .../plugin/fifteenimageprovider.cpp | 87 + .../plugin/fifteenimageprovider.h | 34 + .../plugin/fifteenpuzzleplugin.cpp | 31 + .../fifteenPuzzle/sc-apps-fifteenpuzzle.svgz | Bin 0 -> 1107 bytes applets/fuzzy-clock/Messages.sh | 2 + .../package/contents/config/config.qml | 17 + .../package/contents/config/main.xml | 20 + .../package/contents/ui/FuzzyClock.qml | 314 ++ .../package/contents/ui/configAppearance.qml | 63 + .../fuzzy-clock/package/contents/ui/main.qml | 54 + applets/fuzzy-clock/package/metadata.json | 150 + applets/grouping/CMakeLists.txt | 16 + applets/grouping/Messages.sh | 4 + applets/grouping/container/CMakeLists.txt | 23 + .../container/groupedappletscontainer.cpp | 128 + .../container/groupedappletscontainer.h | 39 + .../container/package/contents/ui/main.qml | 43 + .../grouping/container/package/metadata.json | 145 + applets/grouping/groupingcontainment.cpp | 130 + applets/grouping/groupingcontainment.h | 55 + .../package/contents/applet/CompactApplet.qml | 157 + .../package/contents/config/config.qml | 12 + .../grouping/package/contents/config/main.xml | 11 + .../contents/ui/items/AbstractItem.qml | 22 + .../contents/ui/items/PlasmoidItem.qml | 28 + applets/grouping/package/contents/ui/main.qml | 153 + applets/grouping/package/metadata.json | 148 + applets/katesessions/Messages.sh | 2 + .../contents/ui/KateSessionsItemDelegate.qml | 121 + applets/katesessions/contents/ui/Menu.qml | 42 + applets/katesessions/contents/ui/main.qml | 148 + applets/katesessions/metadata.json | 131 + applets/keyboardindicator/Messages.sh | 2 + .../contents/config/config.qml | 17 + .../contents/config/main.xml | 14 + .../contents/ui/configAppearance.qml | 51 + .../keyboardindicator/contents/ui/main.qml | 101 + applets/keyboardindicator/metadata.json | 151 + applets/kickerdash/metadata.json | 152 + applets/konsoleprofiles/Messages.sh | 2 + .../package/contents/ui/main.qml | 141 + applets/konsoleprofiles/package/metadata.json | 149 + applets/mediaframe/CMakeLists.txt | 15 + applets/mediaframe/Messages.sh | 4 + .../package/contents/config/config.qml | 22 + .../package/contents/config/main.xml | 32 + .../package/contents/ui/ConfigGeneral.qml | 200 ++ .../package/contents/ui/ConfigPaths.qml | 134 + .../mediaframe/package/contents/ui/main.qml | 457 +++ applets/mediaframe/package/metadata.json | 138 + applets/mediaframe/plugin/mediaframe.cpp | 389 +++ applets/mediaframe/plugin/mediaframe.h | 100 + .../mediaframe/plugin/mediaframeplugin.cpp | 24 + applets/notes/CMakeLists.txt | 18 + applets/notes/Messages.sh | 2 + .../notes/package/contents/config/config.qml | 17 + .../notes/package/contents/config/main.xml | 34 + .../package/contents/ui/ShortcutMenuItem.qml | 27 + .../package/contents/ui/configAppearance.qml | 93 + applets/notes/package/contents/ui/main.qml | 656 ++++ applets/notes/package/metadata.json | 150 + applets/notes/plugin/abstractnoteloader.cpp | 18 + applets/notes/plugin/abstractnoteloader.h | 28 + applets/notes/plugin/documenthandler.cpp | 371 ++ applets/notes/plugin/documenthandler.h | 154 + applets/notes/plugin/filesystemnoteloader.cpp | 108 + applets/notes/plugin/filesystemnoteloader.h | 28 + applets/notes/plugin/note.cpp | 33 + applets/notes/plugin/note.h | 42 + applets/notes/plugin/notemanager.cpp | 48 + applets/notes/plugin/notemanager.h | 46 + applets/notes/plugin/notesplugin.cpp | 62 + applets/quicklaunch/CMakeLists.txt | 3 + applets/quicklaunch/Messages.sh | 3 + .../package/contents/config/config.qml | 16 + .../package/contents/config/main.xml | 29 + .../package/contents/ui/ConfigGeneral.qml | 92 + .../package/contents/ui/IconItem.qml | 402 +++ .../quicklaunch/package/contents/ui/Popup.qml | 124 + .../package/contents/ui/UrlModel.qml | 109 + .../quicklaunch/package/contents/ui/layout.js | 108 + .../quicklaunch/package/contents/ui/main.qml | 305 ++ applets/quicklaunch/package/metadata.json | 133 + applets/quicklaunch/plugin/CMakeLists.txt | 13 + applets/quicklaunch/plugin/quicklaunch_p.cpp | 216 ++ applets/quicklaunch/plugin/quicklaunch_p.h | 33 + .../quicklaunch/plugin/quicklaunchplugin.cpp | 24 + applets/timer/CMakeLists.txt | 9 + applets/timer/Messages.sh | 3 + .../timer/package/contents/config/config.qml | 28 + .../timer/package/contents/config/main.xml | 62 + .../contents/ui/CompactRepresentation.qml | 248 ++ .../timer/package/contents/ui/TimerEdit.qml | 177 + .../timer/package/contents/ui/TimerView.qml | 119 + .../package/contents/ui/configAdvanced.qml | 41 + .../package/contents/ui/configAppearance.qml | 98 + .../timer/package/contents/ui/configTimes.qml | 163 + applets/timer/package/contents/ui/main.qml | 213 ++ applets/timer/package/metadata.json | 146 + applets/timer/plasma_applet_timer.notifyrc | 157 + applets/timer/plugin/timerplugin.cpp | 48 + applets/timer/timer.svgz | Bin 0 -> 7503 bytes applets/userswitcher/Messages.sh | 3 + .../package/contents/config/config.qml | 15 + .../package/contents/config/main.xml | 23 + .../contents/ui/ActionListDelegate.qml | 20 + .../package/contents/ui/ListDelegate.qml | 76 + .../package/contents/ui/UserListDelegate.qml | 32 + .../package/contents/ui/configGeneral.qml | 110 + .../userswitcher/package/contents/ui/main.qml | 253 ++ applets/userswitcher/package/metadata.json | 146 + applets/weather/CMakeLists.txt | 30 + applets/weather/Messages.sh | 11 + applets/weather/data/i18n.dat | 18 + .../package/contents/config/config.qml | 35 + .../weather/package/contents/config/main.xml | 54 + .../contents/ui/CompactRepresentation.qml | 79 + .../package/contents/ui/DetailsView.qml | 44 + .../package/contents/ui/ForecastView.qml | 141 + .../contents/ui/FullRepresentation.qml | 118 + .../package/contents/ui/IconAndTextItem.qml | 114 + .../package/contents/ui/NoticesView.qml | 92 + .../package/contents/ui/SwitchPanel.qml | 136 + .../weather/package/contents/ui/TopPanel.qml | 151 + .../contents/ui/config/ConfigAppearance.qml | 89 + .../contents/ui/config/ConfigUnits.qml | 54 + .../ui/config/ConfigWeatherStation.qml | 202 ++ applets/weather/package/contents/ui/main.qml | 435 +++ applets/weather/package/metadata.json | 147 + .../weather/plugin/abstractunitlistmodel.cpp | 58 + .../weather/plugin/abstractunitlistmodel.h | 55 + applets/weather/plugin/locationlistmodel.cpp | 272 ++ applets/weather/plugin/locationlistmodel.h | 106 + applets/weather/plugin/plugin.cpp | 78 + applets/weather/plugin/util.cpp | 76 + applets/weather/plugin/util.h | 48 + applets/weather/weatherapplet.cpp | 64 + applets/weather/weatherapplet.h | 40 + applets/weather/wind-arrows.svgz | Bin 0 -> 2164 bytes applets/webbrowser/CMakeLists.txt | 13 + applets/webbrowser/Messages.sh | 2 + .../package/contents/config/config.qml | 22 + .../package/contents/config/main.xml | 43 + .../package/contents/ui/ConfigAppearance.qml | 126 + .../package/contents/ui/ConfigGeneral.qml | 145 + .../webbrowser/package/contents/ui/main.qml | 290 ++ applets/webbrowser/package/metadata.json | 129 + dict/CMakeLists.txt | 9 + dict/Messages.sh | 2 + dict/dictengine.cpp | 289 ++ dict/dictengine.h | 97 + kdeds/CMakeLists.txt | 4 + kdeds/kameleon/CMakeLists.txt | 49 + kdeds/kameleon/kameleon.cpp | 153 + kdeds/kameleon/kameleon.h | 51 + kdeds/kameleon/kameleon.json | 74 + kdeds/kameleon/kameleon.json.license | 2 + kdeds/kameleon/kameleonhelper.actions | 114 + kdeds/kameleon/kameleonhelper.cpp | 54 + kdeds/kameleon/kameleonhelper.h | 23 + kwin/CMakeLists.txt | 6 + kwin/effects/CMakeLists.txt | 5 + kwin/effects/cube/CMakeLists.txt | 6 + kwin/effects/cube/Messages.sh | 3 + kwin/effects/cube/kcm/CMakeLists.txt | 25 + kwin/effects/cube/kcm/cubeeffectkcm.cpp | 157 + kwin/effects/cube/kcm/cubeeffectkcm.h | 42 + kwin/effects/cube/kcm/cubeeffectkcm.ui | 302 ++ .../effects/cube/kcm/cubeeffectkcm.ui.license | 2 + kwin/effects/cube/kcm/resources.qrc | 5 + kwin/effects/cube/kcm/resources.qrc.license | 2 + .../cube/package/contents/config/main.xml | 34 + .../effects/cube/package/contents/ui/Cube.qml | 45 + .../contents/ui/CubeCameraController.qml | 135 + .../cube/package/contents/ui/CubeFace.qml | 32 + .../cube/package/contents/ui/DesktopView.qml | 31 + .../package/contents/ui/PlaceholderView.qml | 39 + .../cube/package/contents/ui/ScreenView.qml | 167 + .../cube/package/contents/ui/constants.js | 11 + .../effects/cube/package/contents/ui/main.qml | 74 + kwin/effects/cube/package/metadata.json | 142 + kwin/windowswitchers/CMakeLists.txt | 7 + .../big_icons/contents/ui/main.qml | 137 + kwin/windowswitchers/big_icons/metadata.json | 142 + .../compact/contents/ui/main.qml | 190 + kwin/windowswitchers/compact/metadata.json | 142 + .../coverswitch/contents/ui/main.qml | 305 ++ .../windowswitchers/coverswitch/metadata.json | 142 + .../flipswitch/contents/ui/main.qml | 265 ++ kwin/windowswitchers/flipswitch/metadata.json | 142 + .../sidebar/contents/ui/main.qml | 130 + kwin/windowswitchers/sidebar/metadata.json | 132 + plasmacalendarplugins/CMakeLists.txt | 2 + .../alternatecalendar/CMakeLists.txt | 40 + .../alternatecalendar/Messages.sh | 8 + .../alternatecalendarplugin.cpp | 141 + .../alternatecalendarplugin.h | 56 + .../alternatecalendarplugin.json | 92 + .../alternatecalendar/calendarsystem.h | 85 + .../alternatecalendar/config-ICU.h.cmake | 3 + .../alternatecalendar/config/CMakeLists.txt | 6 + .../config/plugin/CMakeLists.txt | 26 + .../config/plugin/configstorage.cpp | 109 + .../config/plugin/configstorage.h | 94 + .../config/qml/AlternateCalendarConfig.qml | 88 + .../config/qml/CMakeLists.txt | 6 + .../provider/abstractcalendarprovider.cpp | 60 + .../provider/abstractcalendarprovider.h | 62 + .../provider/chinesecalendar.cpp | 229 ++ .../provider/chinesecalendar.h | 29 + .../provider/hebrewcalendar.cpp | 120 + .../provider/hebrewcalendar.h | 27 + .../provider/icucalendar_p.cpp | 82 + .../provider/icucalendar_p.h | 72 + .../provider/indiancalendar.cpp | 98 + .../provider/indiancalendar.h | 30 + .../provider/islamiccalendar.cpp | 164 + .../provider/islamiccalendar.h | 31 + .../alternatecalendar/provider/qtcalendar.cpp | 61 + .../alternatecalendar/provider/qtcalendar.h | 31 + .../alternatecalendar/provider/solarutils.h | 3079 +++++++++++++++++ .../astronomical/CMakeLists.txt | 9 + .../astronomical/Messages.sh | 4 + .../astronomical/astronomicaleventsplugin.cpp | 71 + .../astronomical/astronomicaleventsplugin.h | 30 + .../astronomicaleventsplugin.json | 141 + .../astronomical/config/CMakeLists.txt | 2 + .../astronomical/config/plugin/CMakeLists.txt | 8 + .../config/plugin/configplugin.cpp | 24 + .../config/plugin/configstorage.cpp | 28 + .../config/plugin/configstorage.h | 36 + .../config/qml/AstronomicalEventsConfig.qml | 54 + .../astronomical/config/qml/CMakeLists.txt | 1 + po/ar/kwin_effect_cube.po | 56 + po/ar/plasma_addons_engine_dict.po | 23 + po/ar/plasma_addons_profiles_utility.po | 28 + ...plet_org.kde.plasma.addons.katesessions.po | 56 + ...lasma_applet_org.kde.plasma.binaryclock.po | 143 + ...plasma_applet_org.kde.plasma.calculator.po | 96 + ...lasma_applet_org.kde.plasma.colorpicker.po | 152 + po/ar/plasma_applet_org.kde.plasma.comic.po | 411 +++ .../plasma_applet_org.kde.plasma.diskquota.po | 81 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 170 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 887 +++++ ...applet_org.kde.plasma.keyboardindicator.po | 58 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 30 + ...plasma_applet_org.kde.plasma.mediaframe.po | 222 ++ po/ar/plasma_applet_org.kde.plasma.notes.po | 239 ++ ..._applet_org.kde.plasma.private.grouping.po | 23 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 135 + po/ar/plasma_applet_org.kde.plasma.timer.po | 244 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 164 + po/ar/plasma_applet_org.kde.plasma.weather.po | 842 +++++ ...plasma_applet_org.kde.plasma.webbrowser.po | 193 ++ ...lasma_applet_org.kde.plasma_applet_dict.po | 70 + po/ar/plasma_calendar_alternatecalendar.po | 197 ++ po/ar/plasma_calendar_astronomicalevents.po | 35 + po/ar/plasma_runner_CharacterRunner.po | 83 + po/ar/plasma_runner_converterrunner.po | 602 ++++ po/ar/plasma_runner_datetime.po | 167 + po/ar/plasma_runner_katesessions.po | 34 + po/ar/plasma_runner_konsoleprofiles.po | 28 + po/ar/plasma_runner_krunner_dictionary.po | 48 + po/ar/plasma_runner_spellcheckrunner.po | 83 + po/ar/plasma_wallpaper_org.kde.potd.po | 325 ++ po/ast/kwin_effect_cube.po | 46 + po/ast/plasma_addons_engine_dict.po | 23 + po/ast/plasma_addons_profiles_utility.po | 28 + ...plet_org.kde.plasma.addons.katesessions.po | 43 + ...lasma_applet_org.kde.plasma.binaryclock.po | 48 + ...plasma_applet_org.kde.plasma.calculator.po | 74 + ...lasma_applet_org.kde.plasma.colorpicker.po | 147 + po/ast/plasma_applet_org.kde.plasma.comic.po | 245 ++ .../plasma_applet_org.kde.plasma.diskquota.po | 80 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 106 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 880 +++++ ...applet_org.kde.plasma.keyboardindicator.po | 58 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 29 + ...plasma_applet_org.kde.plasma.mediaframe.po | 199 ++ po/ast/plasma_applet_org.kde.plasma.notes.po | 229 ++ ..._applet_org.kde.plasma.private.grouping.po | 23 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 122 + po/ast/plasma_applet_org.kde.plasma.timer.po | 205 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 130 + .../plasma_applet_org.kde.plasma.weather.po | 552 +++ ...plasma_applet_org.kde.plasma.webbrowser.po | 190 + ...lasma_applet_org.kde.plasma_applet_dict.po | 67 + po/ast/plasma_calendar_alternatecalendar.po | 141 + po/ast/plasma_calendar_astronomicalevents.po | 35 + po/ast/plasma_runner_CharacterRunner.po | 80 + po/ast/plasma_runner_converterrunner.po | 36 + po/ast/plasma_runner_datetime.po | 157 + po/ast/plasma_runner_katesessions.po | 33 + po/ast/plasma_runner_konsoleprofiles.po | 28 + po/ast/plasma_runner_krunner_dictionary.po | 47 + po/ast/plasma_runner_spellcheckrunner.po | 78 + po/ast/plasma_wallpaper_org.kde.potd.po | 214 ++ po/az/plasma_addons_engine_dict.po | 23 + po/az/plasma_addons_profiles_utility.po | 28 + ...plet_org.kde.plasma.addons.katesessions.po | 55 + ...lasma_applet_org.kde.plasma.binaryclock.po | 63 + ...plasma_applet_org.kde.plasma.calculator.po | 74 + ...lasma_applet_org.kde.plasma.colorpicker.po | 153 + po/az/plasma_applet_org.kde.plasma.comic.po | 333 ++ .../plasma_applet_org.kde.plasma.diskquota.po | 80 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 106 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 880 +++++ ...applet_org.kde.plasma.keyboardindicator.po | 69 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 29 + ...plasma_applet_org.kde.plasma.mediaframe.po | 203 ++ po/az/plasma_applet_org.kde.plasma.notes.po | 238 ++ ..._applet_org.kde.plasma.private.grouping.po | 23 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 122 + po/az/plasma_applet_org.kde.plasma.timer.po | 212 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 140 + po/az/plasma_applet_org.kde.plasma.weather.po | 610 ++++ ...plasma_applet_org.kde.plasma.webbrowser.po | 194 ++ ...lasma_applet_org.kde.plasma_applet_dict.po | 70 + po/az/plasma_calendar_alternatecalendar.po | 143 + po/az/plasma_calendar_astronomicalevents.po | 35 + po/az/plasma_runner_CharacterRunner.po | 84 + po/az/plasma_runner_converterrunner.po | 42 + po/az/plasma_runner_datetime.po | 174 + po/az/plasma_runner_katesessions.po | 33 + po/az/plasma_runner_konsoleprofiles.po | 28 + po/az/plasma_runner_krunner_dictionary.po | 47 + po/az/plasma_runner_spellcheckrunner.po | 87 + po/az/plasma_wallpaper_org.kde.potd.po | 329 ++ po/be/plasma_addons_engine_dict.po | 27 + ...lasma_applet_org.kde.plasma.binaryclock.po | 94 + ...plasma_applet_org.kde.plasma.calculator.po | 84 + ...lasma_applet_org.kde.plasma.colorpicker.po | 154 + po/be/plasma_applet_org.kde.plasma.comic.po | 268 ++ ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 128 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 1033 ++++++ po/be/plasma_applet_org.kde.plasma.notes.po | 243 ++ po/be/plasma_applet_org.kde.plasma.weather.po | 587 ++++ po/be/plasma_runner_converterrunner.po | 44 + po/be/plasma_runner_spellcheckrunner.po | 86 + po/bg/kwin_effect_cube.po | 50 + po/bg/plasma_addons_engine_dict.po | 23 + po/bg/plasma_addons_profiles_utility.po | 28 + ...plet_org.kde.plasma.addons.katesessions.po | 43 + ...lasma_applet_org.kde.plasma.binaryclock.po | 50 + ...plasma_applet_org.kde.plasma.calculator.po | 76 + ...lasma_applet_org.kde.plasma.colorpicker.po | 150 + po/bg/plasma_applet_org.kde.plasma.comic.po | 245 ++ .../plasma_applet_org.kde.plasma.diskquota.po | 80 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 106 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 881 +++++ ...applet_org.kde.plasma.keyboardindicator.po | 58 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 29 + ...plasma_applet_org.kde.plasma.mediaframe.po | 206 ++ po/bg/plasma_applet_org.kde.plasma.notes.po | 229 ++ ..._applet_org.kde.plasma.private.grouping.po | 23 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 122 + po/bg/plasma_applet_org.kde.plasma.timer.po | 209 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 130 + po/bg/plasma_applet_org.kde.plasma.weather.po | 559 +++ ...plasma_applet_org.kde.plasma.webbrowser.po | 192 + ...lasma_applet_org.kde.plasma_applet_dict.po | 67 + po/bg/plasma_calendar_alternatecalendar.po | 147 + po/bg/plasma_calendar_astronomicalevents.po | 35 + po/bg/plasma_runner_CharacterRunner.po | 83 + po/bg/plasma_runner_converterrunner.po | 39 + po/bg/plasma_runner_datetime.po | 163 + po/bg/plasma_runner_katesessions.po | 33 + po/bg/plasma_runner_konsoleprofiles.po | 28 + po/bg/plasma_runner_krunner_dictionary.po | 47 + po/bg/plasma_runner_spellcheckrunner.po | 80 + po/bg/plasma_wallpaper_org.kde.potd.po | 213 ++ ...plet_org.kde.plasma.addons.katesessions.po | 58 + ...lasma_applet_org.kde.plasma.binaryclock.po | 115 + ...plasma_applet_org.kde.plasma.calculator.po | 77 + ...lasma_applet_org.kde.plasma.colorpicker.po | 165 + po/bs/plasma_applet_org.kde.plasma.comic.po | 442 +++ ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 144 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 896 +++++ ...a_applet_org.kde.plasma.konsoleprofiles.po | 33 + po/bs/plasma_applet_org.kde.plasma.notes.po | 312 ++ po/bs/plasma_applet_org.kde.plasma.timer.po | 213 ++ po/bs/plasma_applet_org.kde.plasma.weather.po | 742 ++++ po/bs/plasma_runner_CharacterRunner.po | 84 + po/bs/plasma_runner_converterrunner.po | 46 + po/bs/plasma_runner_datetime.po | 169 + po/bs/plasma_runner_katesessions.po | 36 + po/bs/plasma_runner_krunner_dictionary.po | 51 + po/bs/plasma_runner_spellcheckrunner.po | 93 + po/ca/kwin_effect_cube.po | 52 + po/ca/plasma_addons_engine_dict.po | 25 + po/ca/plasma_addons_profiles_utility.po | 30 + ...plet_org.kde.plasma.addons.katesessions.po | 48 + ...lasma_applet_org.kde.plasma.binaryclock.po | 51 + ...plasma_applet_org.kde.plasma.calculator.po | 78 + ...lasma_applet_org.kde.plasma.colorpicker.po | 156 + po/ca/plasma_applet_org.kde.plasma.comic.po | 249 ++ .../plasma_applet_org.kde.plasma.diskquota.po | 83 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 110 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 885 +++++ ...applet_org.kde.plasma.keyboardindicator.po | 62 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 32 + ...plasma_applet_org.kde.plasma.mediaframe.po | 205 ++ po/ca/plasma_applet_org.kde.plasma.notes.po | 233 ++ ..._applet_org.kde.plasma.private.grouping.po | 26 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 127 + po/ca/plasma_applet_org.kde.plasma.timer.po | 213 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 134 + po/ca/plasma_applet_org.kde.plasma.weather.po | 564 +++ ...plasma_applet_org.kde.plasma.webbrowser.po | 194 ++ ...lasma_applet_org.kde.plasma_applet_dict.po | 70 + po/ca/plasma_calendar_alternatecalendar.po | 147 + po/ca/plasma_calendar_astronomicalevents.po | 38 + po/ca/plasma_runner_CharacterRunner.po | 86 + po/ca/plasma_runner_converterrunner.po | 43 + po/ca/plasma_runner_datetime.po | 168 + po/ca/plasma_runner_katesessions.po | 37 + po/ca/plasma_runner_konsoleprofiles.po | 31 + po/ca/plasma_runner_krunner_dictionary.po | 50 + po/ca/plasma_runner_spellcheckrunner.po | 84 + po/ca/plasma_wallpaper_org.kde.potd.po | 213 ++ po/ca@valencia/kwin_effect_cube.po | 52 + po/ca@valencia/plasma_addons_engine_dict.po | 25 + .../plasma_addons_profiles_utility.po | 30 + ...plet_org.kde.plasma.addons.katesessions.po | 48 + ...lasma_applet_org.kde.plasma.binaryclock.po | 51 + ...plasma_applet_org.kde.plasma.calculator.po | 78 + ...lasma_applet_org.kde.plasma.colorpicker.po | 156 + .../plasma_applet_org.kde.plasma.comic.po | 249 ++ .../plasma_applet_org.kde.plasma.diskquota.po | 83 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 110 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 885 +++++ ...applet_org.kde.plasma.keyboardindicator.po | 62 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 32 + ...plasma_applet_org.kde.plasma.mediaframe.po | 205 ++ .../plasma_applet_org.kde.plasma.notes.po | 233 ++ ..._applet_org.kde.plasma.private.grouping.po | 26 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 128 + .../plasma_applet_org.kde.plasma.timer.po | 213 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 134 + .../plasma_applet_org.kde.plasma.weather.po | 564 +++ ...plasma_applet_org.kde.plasma.webbrowser.po | 194 ++ ...lasma_applet_org.kde.plasma_applet_dict.po | 70 + .../plasma_calendar_alternatecalendar.po | 147 + .../plasma_calendar_astronomicalevents.po | 38 + .../plasma_runner_CharacterRunner.po | 86 + .../plasma_runner_converterrunner.po | 43 + po/ca@valencia/plasma_runner_datetime.po | 168 + po/ca@valencia/plasma_runner_katesessions.po | 37 + .../plasma_runner_konsoleprofiles.po | 31 + .../plasma_runner_krunner_dictionary.po | 50 + .../plasma_runner_spellcheckrunner.po | 84 + .../plasma_wallpaper_org.kde.potd.po | 214 ++ po/cs/kwin_effect_cube.po | 47 + po/cs/plasma_addons_engine_dict.po | 23 + po/cs/plasma_addons_profiles_utility.po | 28 + ...plet_org.kde.plasma.addons.katesessions.po | 44 + ...lasma_applet_org.kde.plasma.binaryclock.po | 49 + ...plasma_applet_org.kde.plasma.calculator.po | 74 + ...lasma_applet_org.kde.plasma.colorpicker.po | 148 + po/cs/plasma_applet_org.kde.plasma.comic.po | 251 ++ .../plasma_applet_org.kde.plasma.diskquota.po | 81 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 107 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 883 +++++ ...applet_org.kde.plasma.keyboardindicator.po | 58 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 29 + ...plasma_applet_org.kde.plasma.mediaframe.po | 200 ++ po/cs/plasma_applet_org.kde.plasma.notes.po | 230 ++ ..._applet_org.kde.plasma.private.grouping.po | 23 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 123 + po/cs/plasma_applet_org.kde.plasma.timer.po | 209 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 132 + po/cs/plasma_applet_org.kde.plasma.weather.po | 556 +++ ...plasma_applet_org.kde.plasma.webbrowser.po | 190 + ...lasma_applet_org.kde.plasma_applet_dict.po | 67 + po/cs/plasma_calendar_alternatecalendar.po | 142 + po/cs/plasma_calendar_astronomicalevents.po | 35 + po/cs/plasma_runner_CharacterRunner.po | 84 + po/cs/plasma_runner_converterrunner.po | 41 + po/cs/plasma_runner_datetime.po | 160 + po/cs/plasma_runner_katesessions.po | 33 + po/cs/plasma_runner_konsoleprofiles.po | 28 + po/cs/plasma_runner_krunner_dictionary.po | 48 + po/cs/plasma_runner_spellcheckrunner.po | 79 + po/cs/plasma_wallpaper_org.kde.potd.po | 209 ++ ...lasma_applet_org.kde.plasma.colorpicker.po | 153 + po/csb/plasma_applet_org.kde.plasma.comic.po | 375 ++ ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 151 + po/csb/plasma_applet_org.kde.plasma.notes.po | 270 ++ po/da/plasma_addons_engine_dict.po | 23 + ...plet_org.kde.plasma.addons.katesessions.po | 56 + ...lasma_applet_org.kde.plasma.binaryclock.po | 160 + ...plasma_applet_org.kde.plasma.calculator.po | 95 + ...lasma_applet_org.kde.plasma.colorpicker.po | 161 + po/da/plasma_applet_org.kde.plasma.comic.po | 460 +++ .../plasma_applet_org.kde.plasma.diskquota.po | 81 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 160 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 1060 ++++++ ...applet_org.kde.plasma.keyboardindicator.po | 68 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 29 + ...plasma_applet_org.kde.plasma.mediaframe.po | 216 ++ po/da/plasma_applet_org.kde.plasma.notes.po | 329 ++ ..._applet_org.kde.plasma.private.grouping.po | 23 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 131 + po/da/plasma_applet_org.kde.plasma.timer.po | 258 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 160 + po/da/plasma_applet_org.kde.plasma.weather.po | 755 ++++ ...lasma_applet_org.kde.plasma_applet_dict.po | 73 + po/da/plasma_calendar_astronomicalevents.po | 35 + po/da/plasma_runner_CharacterRunner.po | 84 + po/da/plasma_runner_converterrunner.po | 605 ++++ po/da/plasma_runner_datetime.po | 169 + po/da/plasma_runner_katesessions.po | 33 + po/da/plasma_runner_konsoleprofiles.po | 28 + po/da/plasma_runner_krunner_dictionary.po | 48 + po/da/plasma_runner_spellcheckrunner.po | 95 + po/da/plasma_wallpaper_org.kde.potd.po | 322 ++ po/de/kwin_effect_cube.po | 53 + po/de/plasma_addons_engine_dict.po | 22 + po/de/plasma_addons_profiles_utility.po | 30 + ...plet_org.kde.plasma.addons.katesessions.po | 53 + ...lasma_applet_org.kde.plasma.binaryclock.po | 148 + ...plasma_applet_org.kde.plasma.calculator.po | 100 + ...lasma_applet_org.kde.plasma.colorpicker.po | 165 + po/de/plasma_applet_org.kde.plasma.comic.po | 492 +++ .../plasma_applet_org.kde.plasma.diskquota.po | 81 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 178 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 1057 ++++++ ...applet_org.kde.plasma.keyboardindicator.po | 69 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 27 + ...plasma_applet_org.kde.plasma.mediaframe.po | 217 ++ po/de/plasma_applet_org.kde.plasma.notes.po | 335 ++ ..._applet_org.kde.plasma.private.grouping.po | 22 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 130 + po/de/plasma_applet_org.kde.plasma.timer.po | 259 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 157 + po/de/plasma_applet_org.kde.plasma.weather.po | 757 ++++ ...plasma_applet_org.kde.plasma.webbrowser.po | 199 ++ ...lasma_applet_org.kde.plasma_applet_dict.po | 72 + po/de/plasma_calendar_alternatecalendar.po | 146 + po/de/plasma_calendar_astronomicalevents.po | 34 + po/de/plasma_runner_CharacterRunner.po | 84 + po/de/plasma_runner_converterrunner.po | 600 ++++ po/de/plasma_runner_datetime.po | 177 + po/de/plasma_runner_katesessions.po | 29 + po/de/plasma_runner_konsoleprofiles.po | 27 + po/de/plasma_runner_krunner_dictionary.po | 47 + po/de/plasma_runner_spellcheckrunner.po | 93 + po/de/plasma_wallpaper_org.kde.potd.po | 331 ++ ...plet_org.kde.plasma.addons.katesessions.po | 56 + ...lasma_applet_org.kde.plasma.binaryclock.po | 183 + ...plasma_applet_org.kde.plasma.calculator.po | 105 + ...lasma_applet_org.kde.plasma.colorpicker.po | 168 + po/el/plasma_applet_org.kde.plasma.comic.po | 542 +++ .../plasma_applet_org.kde.plasma.diskquota.po | 89 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 183 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 1095 ++++++ ...a_applet_org.kde.plasma.konsoleprofiles.po | 31 + ...plasma_applet_org.kde.plasma.mediaframe.po | 250 ++ po/el/plasma_applet_org.kde.plasma.notes.po | 348 ++ ..._applet_org.kde.plasma.private.grouping.po | 23 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 149 + po/el/plasma_applet_org.kde.plasma.timer.po | 284 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 169 + po/el/plasma_applet_org.kde.plasma.weather.po | 1025 ++++++ po/el/plasma_runner_CharacterRunner.po | 84 + po/el/plasma_runner_converterrunner.po | 615 ++++ po/el/plasma_runner_datetime.po | 167 + po/el/plasma_runner_katesessions.po | 33 + po/el/plasma_runner_krunner_dictionary.po | 49 + po/el/plasma_runner_spellcheckrunner.po | 96 + po/en_GB/kwin_effect_cube.po | 50 + po/en_GB/plasma_addons_engine_dict.po | 23 + po/en_GB/plasma_addons_profiles_utility.po | 28 + ...plet_org.kde.plasma.addons.katesessions.po | 55 + ...lasma_applet_org.kde.plasma.binaryclock.po | 130 + ...plasma_applet_org.kde.plasma.calculator.po | 74 + ...lasma_applet_org.kde.plasma.colorpicker.po | 161 + .../plasma_applet_org.kde.plasma.comic.po | 462 +++ .../plasma_applet_org.kde.plasma.diskquota.po | 80 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 158 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 930 +++++ ...applet_org.kde.plasma.keyboardindicator.po | 70 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 29 + ...plasma_applet_org.kde.plasma.mediaframe.po | 215 ++ .../plasma_applet_org.kde.plasma.notes.po | 315 ++ ..._applet_org.kde.plasma.private.grouping.po | 23 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 130 + .../plasma_applet_org.kde.plasma.timer.po | 234 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 154 + .../plasma_applet_org.kde.plasma.weather.po | 747 ++++ ...plasma_applet_org.kde.plasma.webbrowser.po | 192 + ...lasma_applet_org.kde.plasma_applet_dict.po | 70 + po/en_GB/plasma_calendar_alternatecalendar.po | 193 ++ .../plasma_calendar_astronomicalevents.po | 35 + po/en_GB/plasma_runner_CharacterRunner.po | 84 + po/en_GB/plasma_runner_converterrunner.po | 56 + po/en_GB/plasma_runner_datetime.po | 175 + po/en_GB/plasma_runner_katesessions.po | 33 + po/en_GB/plasma_runner_konsoleprofiles.po | 28 + po/en_GB/plasma_runner_krunner_dictionary.po | 48 + po/en_GB/plasma_runner_spellcheckrunner.po | 96 + po/en_GB/plasma_wallpaper_org.kde.potd.po | 324 ++ po/eo/kwin_effect_cube.po | 50 + po/eo/plasma_addons_engine_dict.po | 23 + po/eo/plasma_addons_profiles_utility.po | 28 + ...plet_org.kde.plasma.addons.katesessions.po | 44 + ...lasma_applet_org.kde.plasma.binaryclock.po | 50 + ...plasma_applet_org.kde.plasma.calculator.po | 76 + ...lasma_applet_org.kde.plasma.colorpicker.po | 151 + po/eo/plasma_applet_org.kde.plasma.comic.po | 247 ++ .../plasma_applet_org.kde.plasma.diskquota.po | 81 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 108 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 882 +++++ ...applet_org.kde.plasma.keyboardindicator.po | 59 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 30 + ...plasma_applet_org.kde.plasma.mediaframe.po | 200 ++ po/eo/plasma_applet_org.kde.plasma.notes.po | 231 ++ ..._applet_org.kde.plasma.private.grouping.po | 24 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 123 + po/eo/plasma_applet_org.kde.plasma.timer.po | 211 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 131 + po/eo/plasma_applet_org.kde.plasma.weather.po | 571 +++ ...plasma_applet_org.kde.plasma.webbrowser.po | 193 ++ ...lasma_applet_org.kde.plasma_applet_dict.po | 68 + po/eo/plasma_calendar_alternatecalendar.po | 146 + po/eo/plasma_calendar_astronomicalevents.po | 35 + po/eo/plasma_runner_CharacterRunner.po | 85 + po/eo/plasma_runner_converterrunner.po | 41 + po/eo/plasma_runner_datetime.po | 164 + po/eo/plasma_runner_katesessions.po | 35 + po/eo/plasma_runner_konsoleprofiles.po | 28 + po/eo/plasma_runner_krunner_dictionary.po | 48 + po/eo/plasma_runner_spellcheckrunner.po | 82 + po/eo/plasma_wallpaper_org.kde.potd.po | 213 ++ po/es/kwin_effect_cube.po | 52 + po/es/plasma_addons_engine_dict.po | 25 + po/es/plasma_addons_profiles_utility.po | 30 + ...plet_org.kde.plasma.addons.katesessions.po | 57 + ...lasma_applet_org.kde.plasma.binaryclock.po | 120 + ...plasma_applet_org.kde.plasma.calculator.po | 81 + ...lasma_applet_org.kde.plasma.colorpicker.po | 159 + po/es/plasma_applet_org.kde.plasma.comic.po | 399 +++ .../plasma_applet_org.kde.plasma.diskquota.po | 82 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 154 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 941 +++++ ...applet_org.kde.plasma.keyboardindicator.po | 69 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 32 + ...plasma_applet_org.kde.plasma.mediaframe.po | 219 ++ po/es/plasma_applet_org.kde.plasma.notes.po | 256 ++ ..._applet_org.kde.plasma.private.grouping.po | 26 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 132 + po/es/plasma_applet_org.kde.plasma.timer.po | 242 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 156 + po/es/plasma_applet_org.kde.plasma.weather.po | 758 ++++ ...plasma_applet_org.kde.plasma.webbrowser.po | 194 ++ ...lasma_applet_org.kde.plasma_applet_dict.po | 72 + po/es/plasma_calendar_alternatecalendar.po | 195 ++ po/es/plasma_calendar_astronomicalevents.po | 37 + po/es/plasma_runner_CharacterRunner.po | 86 + po/es/plasma_runner_converterrunner.po | 49 + po/es/plasma_runner_datetime.po | 178 + po/es/plasma_runner_katesessions.po | 34 + po/es/plasma_runner_konsoleprofiles.po | 30 + po/es/plasma_runner_krunner_dictionary.po | 48 + po/es/plasma_runner_spellcheckrunner.po | 100 + po/es/plasma_wallpaper_org.kde.potd.po | 331 ++ po/et/plasma_addons_engine_dict.po | 23 + ...plet_org.kde.plasma.addons.katesessions.po | 56 + ...lasma_applet_org.kde.plasma.binaryclock.po | 159 + ...plasma_applet_org.kde.plasma.calculator.po | 99 + ...lasma_applet_org.kde.plasma.colorpicker.po | 163 + po/et/plasma_applet_org.kde.plasma.comic.po | 533 +++ .../plasma_applet_org.kde.plasma.diskquota.po | 89 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 182 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 1050 ++++++ ...applet_org.kde.plasma.keyboardindicator.po | 69 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 29 + ...plasma_applet_org.kde.plasma.mediaframe.po | 227 ++ po/et/plasma_applet_org.kde.plasma.notes.po | 336 ++ ..._applet_org.kde.plasma.private.grouping.po | 23 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 140 + po/et/plasma_applet_org.kde.plasma.timer.po | 267 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 163 + po/et/plasma_applet_org.kde.plasma.weather.po | 912 +++++ ...lasma_applet_org.kde.plasma_applet_dict.po | 82 + po/et/plasma_calendar_astronomicalevents.po | 35 + po/et/plasma_runner_CharacterRunner.po | 83 + po/et/plasma_runner_converterrunner.po | 608 ++++ po/et/plasma_runner_datetime.po | 165 + po/et/plasma_runner_katesessions.po | 37 + po/et/plasma_runner_konsoleprofiles.po | 28 + po/et/plasma_runner_krunner_dictionary.po | 47 + po/et/plasma_runner_spellcheckrunner.po | 98 + po/et/plasma_wallpaper_org.kde.potd.po | 318 ++ po/eu/kwin_effect_cube.po | 53 + po/eu/plasma_addons_engine_dict.po | 26 + po/eu/plasma_addons_profiles_utility.po | 31 + ...plet_org.kde.plasma.addons.katesessions.po | 59 + ...lasma_applet_org.kde.plasma.binaryclock.po | 88 + ...plasma_applet_org.kde.plasma.calculator.po | 97 + ...lasma_applet_org.kde.plasma.colorpicker.po | 157 + po/eu/plasma_applet_org.kde.plasma.comic.po | 393 +++ .../plasma_applet_org.kde.plasma.diskquota.po | 84 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 163 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 950 +++++ ...applet_org.kde.plasma.keyboardindicator.po | 70 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 32 + ...plasma_applet_org.kde.plasma.mediaframe.po | 219 ++ po/eu/plasma_applet_org.kde.plasma.notes.po | 235 ++ ..._applet_org.kde.plasma.private.grouping.po | 26 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 135 + po/eu/plasma_applet_org.kde.plasma.timer.po | 253 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 162 + po/eu/plasma_applet_org.kde.plasma.weather.po | 759 ++++ ...plasma_applet_org.kde.plasma.webbrowser.po | 195 ++ ...lasma_applet_org.kde.plasma_applet_dict.po | 76 + po/eu/plasma_calendar_alternatecalendar.po | 196 ++ po/eu/plasma_calendar_astronomicalevents.po | 39 + po/eu/plasma_runner_CharacterRunner.po | 88 + po/eu/plasma_runner_converterrunner.po | 45 + po/eu/plasma_runner_datetime.po | 173 + po/eu/plasma_runner_katesessions.po | 36 + po/eu/plasma_runner_konsoleprofiles.po | 31 + po/eu/plasma_runner_krunner_dictionary.po | 50 + po/eu/plasma_runner_spellcheckrunner.po | 93 + po/eu/plasma_wallpaper_org.kde.potd.po | 328 ++ po/fi/kwin_effect_cube.po | 50 + po/fi/plasma_addons_engine_dict.po | 23 + po/fi/plasma_addons_profiles_utility.po | 28 + ...plet_org.kde.plasma.addons.katesessions.po | 58 + ...lasma_applet_org.kde.plasma.binaryclock.po | 156 + ...plasma_applet_org.kde.plasma.calculator.po | 97 + ...lasma_applet_org.kde.plasma.colorpicker.po | 165 + po/fi/plasma_applet_org.kde.plasma.comic.po | 464 +++ .../plasma_applet_org.kde.plasma.diskquota.po | 81 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 166 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 1057 ++++++ ...applet_org.kde.plasma.keyboardindicator.po | 70 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 29 + ...plasma_applet_org.kde.plasma.mediaframe.po | 216 ++ po/fi/plasma_applet_org.kde.plasma.notes.po | 339 ++ ..._applet_org.kde.plasma.private.grouping.po | 23 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 132 + po/fi/plasma_applet_org.kde.plasma.timer.po | 257 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 157 + po/fi/plasma_applet_org.kde.plasma.weather.po | 755 ++++ ...plasma_applet_org.kde.plasma.webbrowser.po | 197 ++ ...lasma_applet_org.kde.plasma_applet_dict.po | 70 + po/fi/plasma_calendar_alternatecalendar.po | 146 + po/fi/plasma_calendar_astronomicalevents.po | 35 + po/fi/plasma_runner_CharacterRunner.po | 84 + po/fi/plasma_runner_converterrunner.po | 47 + po/fi/plasma_runner_datetime.po | 173 + po/fi/plasma_runner_katesessions.po | 36 + po/fi/plasma_runner_konsoleprofiles.po | 28 + po/fi/plasma_runner_krunner_dictionary.po | 48 + po/fi/plasma_runner_spellcheckrunner.po | 97 + po/fi/plasma_wallpaper_org.kde.potd.po | 331 ++ po/fr/kwin_effect_cube.po | 47 + po/fr/plasma_addons_engine_dict.po | 23 + po/fr/plasma_addons_profiles_utility.po | 25 + ...plet_org.kde.plasma.addons.katesessions.po | 63 + ...lasma_applet_org.kde.plasma.binaryclock.po | 138 + ...plasma_applet_org.kde.plasma.calculator.po | 103 + ...lasma_applet_org.kde.plasma.colorpicker.po | 171 + po/fr/plasma_applet_org.kde.plasma.comic.po | 456 +++ .../plasma_applet_org.kde.plasma.diskquota.po | 85 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 165 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 1064 ++++++ ...applet_org.kde.plasma.keyboardindicator.po | 72 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 35 + ...plasma_applet_org.kde.plasma.mediaframe.po | 218 ++ po/fr/plasma_applet_org.kde.plasma.notes.po | 325 ++ ..._applet_org.kde.plasma.private.grouping.po | 23 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 136 + po/fr/plasma_applet_org.kde.plasma.timer.po | 265 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 159 + po/fr/plasma_applet_org.kde.plasma.weather.po | 764 ++++ ...plasma_applet_org.kde.plasma.webbrowser.po | 189 + ...lasma_applet_org.kde.plasma_applet_dict.po | 72 + po/fr/plasma_calendar_alternatecalendar.po | 191 + po/fr/plasma_calendar_astronomicalevents.po | 36 + po/fr/plasma_runner_CharacterRunner.po | 90 + po/fr/plasma_runner_converterrunner.po | 51 + po/fr/plasma_runner_datetime.po | 184 + po/fr/plasma_runner_katesessions.po | 37 + po/fr/plasma_runner_konsoleprofiles.po | 28 + po/fr/plasma_runner_krunner_dictionary.po | 53 + po/fr/plasma_runner_spellcheckrunner.po | 108 + po/fr/plasma_wallpaper_org.kde.potd.po | 334 ++ ...lasma_applet_org.kde.plasma.binaryclock.po | 204 ++ ...plasma_applet_org.kde.plasma.calculator.po | 94 + ...lasma_applet_org.kde.plasma.colorpicker.po | 164 + po/ga/plasma_applet_org.kde.plasma.comic.po | 435 +++ ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 178 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 1389 ++++++++ ...a_applet_org.kde.plasma.konsoleprofiles.po | 30 + po/ga/plasma_applet_org.kde.plasma.notes.po | 336 ++ po/ga/plasma_applet_org.kde.plasma.timer.po | 269 ++ po/ga/plasma_applet_org.kde.plasma.weather.po | 929 +++++ po/ga/plasma_runner_CharacterRunner.po | 82 + po/ga/plasma_runner_converterrunner.po | 427 +++ po/ga/plasma_runner_datetime.po | 166 + po/ga/plasma_runner_katesessions.po | 36 + po/ga/plasma_runner_krunner_dictionary.po | 48 + po/ga/plasma_runner_spellcheckrunner.po | 93 + po/gl/kwin_effect_cube.po | 50 + po/gl/plasma_addons_engine_dict.po | 22 + po/gl/plasma_addons_profiles_utility.po | 28 + ...plet_org.kde.plasma.addons.katesessions.po | 56 + ...lasma_applet_org.kde.plasma.binaryclock.po | 120 + ...plasma_applet_org.kde.plasma.calculator.po | 89 + ...lasma_applet_org.kde.plasma.colorpicker.po | 166 + po/gl/plasma_applet_org.kde.plasma.comic.po | 410 +++ .../plasma_applet_org.kde.plasma.diskquota.po | 81 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 150 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 1054 ++++++ ...applet_org.kde.plasma.keyboardindicator.po | 67 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 29 + ...plasma_applet_org.kde.plasma.mediaframe.po | 219 ++ po/gl/plasma_applet_org.kde.plasma.notes.po | 308 ++ ..._applet_org.kde.plasma.private.grouping.po | 22 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 132 + po/gl/plasma_applet_org.kde.plasma.timer.po | 257 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 158 + po/gl/plasma_applet_org.kde.plasma.weather.po | 757 ++++ ...plasma_applet_org.kde.plasma.webbrowser.po | 192 + ...lasma_applet_org.kde.plasma_applet_dict.po | 72 + po/gl/plasma_calendar_alternatecalendar.po | 146 + po/gl/plasma_calendar_astronomicalevents.po | 34 + po/gl/plasma_runner_CharacterRunner.po | 86 + po/gl/plasma_runner_converterrunner.po | 43 + po/gl/plasma_runner_datetime.po | 170 + po/gl/plasma_runner_katesessions.po | 35 + po/gl/plasma_runner_konsoleprofiles.po | 27 + po/gl/plasma_runner_krunner_dictionary.po | 48 + po/gl/plasma_runner_spellcheckrunner.po | 95 + po/gl/plasma_wallpaper_org.kde.potd.po | 316 ++ po/he/kwin_effect_cube.po | 53 + po/he/plasma_addons_engine_dict.po | 25 + po/he/plasma_addons_profiles_utility.po | 30 + ...plet_org.kde.plasma.addons.katesessions.po | 55 + ...lasma_applet_org.kde.plasma.binaryclock.po | 78 + ...plasma_applet_org.kde.plasma.calculator.po | 78 + ...lasma_applet_org.kde.plasma.colorpicker.po | 158 + po/he/plasma_applet_org.kde.plasma.comic.po | 282 ++ .../plasma_applet_org.kde.plasma.diskquota.po | 83 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 143 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 891 +++++ ...applet_org.kde.plasma.keyboardindicator.po | 60 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 32 + ...plasma_applet_org.kde.plasma.mediaframe.po | 225 ++ po/he/plasma_applet_org.kde.plasma.notes.po | 239 ++ ..._applet_org.kde.plasma.private.grouping.po | 22 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 137 + po/he/plasma_applet_org.kde.plasma.timer.po | 242 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 168 + po/he/plasma_applet_org.kde.plasma.weather.po | 588 ++++ ...plasma_applet_org.kde.plasma.webbrowser.po | 193 ++ ...lasma_applet_org.kde.plasma_applet_dict.po | 69 + po/he/plasma_calendar_alternatecalendar.po | 147 + po/he/plasma_calendar_astronomicalevents.po | 37 + po/he/plasma_runner_CharacterRunner.po | 83 + po/he/plasma_runner_converterrunner.po | 40 + po/he/plasma_runner_datetime.po | 162 + po/he/plasma_runner_katesessions.po | 35 + po/he/plasma_runner_konsoleprofiles.po | 30 + po/he/plasma_runner_krunner_dictionary.po | 49 + po/he/plasma_runner_spellcheckrunner.po | 90 + po/he/plasma_wallpaper_org.kde.potd.po | 209 ++ po/hi/plasma_addons_engine_dict.po | 23 + ...plet_org.kde.plasma.addons.katesessions.po | 57 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 881 +++++ po/hi/plasma_runner_krunner_dictionary.po | 49 + po/hi/plasma_runner_spellcheckrunner.po | 87 + ...lasma_applet_org.kde.plasma.binaryclock.po | 118 + ...plasma_applet_org.kde.plasma.calculator.po | 107 + ...lasma_applet_org.kde.plasma.colorpicker.po | 168 + po/hr/plasma_applet_org.kde.plasma.comic.po | 253 ++ ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 161 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 1309 +++++++ po/hr/plasma_applet_org.kde.plasma.notes.po | 316 ++ po/hr/plasma_applet_org.kde.plasma.timer.po | 255 ++ po/hr/plasma_applet_org.kde.plasma.weather.po | 752 ++++ po/hr/plasma_runner_CharacterRunner.po | 89 + po/hr/plasma_runner_converterrunner.po | 48 + po/hr/plasma_runner_datetime.po | 170 + po/hr/plasma_runner_katesessions.po | 36 + po/hr/plasma_runner_spellcheckrunner.po | 83 + po/hu/kwin_effect_cube.po | 50 + po/hu/plasma_addons_engine_dict.po | 23 + po/hu/plasma_addons_profiles_utility.po | 28 + ...plet_org.kde.plasma.addons.katesessions.po | 59 + ...lasma_applet_org.kde.plasma.binaryclock.po | 118 + ...plasma_applet_org.kde.plasma.calculator.po | 101 + ...lasma_applet_org.kde.plasma.colorpicker.po | 163 + po/hu/plasma_applet_org.kde.plasma.comic.po | 475 +++ .../plasma_applet_org.kde.plasma.diskquota.po | 81 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 155 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 1052 ++++++ ...applet_org.kde.plasma.keyboardindicator.po | 68 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 30 + ...plasma_applet_org.kde.plasma.mediaframe.po | 204 ++ po/hu/plasma_applet_org.kde.plasma.notes.po | 301 ++ ..._applet_org.kde.plasma.private.grouping.po | 23 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 124 + po/hu/plasma_applet_org.kde.plasma.timer.po | 251 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 142 + po/hu/plasma_applet_org.kde.plasma.weather.po | 700 ++++ ...plasma_applet_org.kde.plasma.webbrowser.po | 192 + ...lasma_applet_org.kde.plasma_applet_dict.po | 73 + po/hu/plasma_calendar_alternatecalendar.po | 145 + po/hu/plasma_calendar_astronomicalevents.po | 35 + po/hu/plasma_runner_CharacterRunner.po | 85 + po/hu/plasma_runner_converterrunner.po | 52 + po/hu/plasma_runner_datetime.po | 172 + po/hu/plasma_runner_katesessions.po | 32 + po/hu/plasma_runner_konsoleprofiles.po | 28 + po/hu/plasma_runner_krunner_dictionary.po | 49 + po/hu/plasma_runner_spellcheckrunner.po | 97 + po/hu/plasma_wallpaper_org.kde.potd.po | 317 ++ po/ia/kwin_effect_cube.po | 51 + po/ia/plasma_addons_engine_dict.po | 23 + po/ia/plasma_addons_profiles_utility.po | 28 + ...plet_org.kde.plasma.addons.katesessions.po | 55 + ...lasma_applet_org.kde.plasma.binaryclock.po | 66 + ...plasma_applet_org.kde.plasma.calculator.po | 75 + ...lasma_applet_org.kde.plasma.colorpicker.po | 155 + po/ia/plasma_applet_org.kde.plasma.comic.po | 360 ++ .../plasma_applet_org.kde.plasma.diskquota.po | 80 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 122 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 886 +++++ ...applet_org.kde.plasma.keyboardindicator.po | 67 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 29 + ...plasma_applet_org.kde.plasma.mediaframe.po | 215 ++ po/ia/plasma_applet_org.kde.plasma.notes.po | 232 ++ ..._applet_org.kde.plasma.private.grouping.po | 23 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 128 + po/ia/plasma_applet_org.kde.plasma.timer.po | 236 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 144 + po/ia/plasma_applet_org.kde.plasma.weather.po | 709 ++++ ...plasma_applet_org.kde.plasma.webbrowser.po | 192 + ...lasma_applet_org.kde.plasma_applet_dict.po | 70 + po/ia/plasma_calendar_alternatecalendar.po | 193 ++ po/ia/plasma_calendar_astronomicalevents.po | 35 + po/ia/plasma_runner_CharacterRunner.po | 83 + po/ia/plasma_runner_converterrunner.po | 39 + po/ia/plasma_runner_datetime.po | 170 + po/ia/plasma_runner_katesessions.po | 33 + po/ia/plasma_runner_konsoleprofiles.po | 28 + po/ia/plasma_runner_krunner_dictionary.po | 47 + po/ia/plasma_runner_spellcheckrunner.po | 90 + po/ia/plasma_wallpaper_org.kde.potd.po | 326 ++ po/id/plasma_addons_engine_dict.po | 22 + ...plet_org.kde.plasma.addons.katesessions.po | 85 + ...lasma_applet_org.kde.plasma.binaryclock.po | 82 + ...plasma_applet_org.kde.plasma.calculator.po | 73 + ...lasma_applet_org.kde.plasma.colorpicker.po | 154 + po/id/plasma_applet_org.kde.plasma.comic.po | 379 ++ .../plasma_applet_org.kde.plasma.diskquota.po | 88 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 131 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 892 +++++ ...applet_org.kde.plasma.keyboardindicator.po | 71 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 28 + ...plasma_applet_org.kde.plasma.mediaframe.po | 216 ++ po/id/plasma_applet_org.kde.plasma.notes.po | 241 ++ ..._applet_org.kde.plasma.private.grouping.po | 22 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 132 + po/id/plasma_applet_org.kde.plasma.timer.po | 233 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 159 + po/id/plasma_applet_org.kde.plasma.weather.po | 722 ++++ ...lasma_applet_org.kde.plasma_applet_dict.po | 73 + po/id/plasma_calendar_alternatecalendar.po | 143 + po/id/plasma_calendar_astronomicalevents.po | 34 + po/id/plasma_runner_CharacterRunner.po | 83 + po/id/plasma_runner_converterrunner.po | 43 + po/id/plasma_runner_datetime.po | 160 + po/id/plasma_runner_katesessions.po | 32 + po/id/plasma_runner_konsoleprofiles.po | 27 + po/id/plasma_runner_krunner_dictionary.po | 46 + po/id/plasma_runner_spellcheckrunner.po | 89 + po/id/plasma_wallpaper_org.kde.potd.po | 324 ++ po/is/kwin_effect_cube.po | 51 + po/is/plasma_addons_engine_dict.po | 23 + po/is/plasma_addons_profiles_utility.po | 28 + ...plet_org.kde.plasma.addons.katesessions.po | 43 + ...lasma_applet_org.kde.plasma.binaryclock.po | 122 + ...plasma_applet_org.kde.plasma.calculator.po | 88 + ...lasma_applet_org.kde.plasma.colorpicker.po | 168 + po/is/plasma_applet_org.kde.plasma.comic.po | 348 ++ .../plasma_applet_org.kde.plasma.diskquota.po | 80 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 137 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 1089 ++++++ ...applet_org.kde.plasma.keyboardindicator.po | 58 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 29 + ...plasma_applet_org.kde.plasma.mediaframe.po | 200 ++ po/is/plasma_applet_org.kde.plasma.notes.po | 316 ++ ..._applet_org.kde.plasma.private.grouping.po | 23 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 123 + po/is/plasma_applet_org.kde.plasma.timer.po | 251 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 136 + po/is/plasma_applet_org.kde.plasma.weather.po | 693 ++++ ...plasma_applet_org.kde.plasma.webbrowser.po | 192 + ...lasma_applet_org.kde.plasma_applet_dict.po | 67 + po/is/plasma_calendar_alternatecalendar.po | 145 + po/is/plasma_calendar_astronomicalevents.po | 35 + po/is/plasma_runner_CharacterRunner.po | 82 + po/is/plasma_runner_converterrunner.po | 43 + po/is/plasma_runner_datetime.po | 169 + po/is/plasma_runner_katesessions.po | 37 + po/is/plasma_runner_konsoleprofiles.po | 28 + po/is/plasma_runner_krunner_dictionary.po | 47 + po/is/plasma_runner_spellcheckrunner.po | 89 + po/is/plasma_wallpaper_org.kde.potd.po | 212 ++ po/it/kwin_effect_cube.po | 50 + po/it/plasma_addons_engine_dict.po | 23 + po/it/plasma_addons_profiles_utility.po | 28 + ...plet_org.kde.plasma.addons.katesessions.po | 58 + ...lasma_applet_org.kde.plasma.binaryclock.po | 152 + ...plasma_applet_org.kde.plasma.calculator.po | 97 + ...lasma_applet_org.kde.plasma.colorpicker.po | 165 + po/it/plasma_applet_org.kde.plasma.comic.po | 448 +++ .../plasma_applet_org.kde.plasma.diskquota.po | 80 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 162 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 1062 ++++++ ...applet_org.kde.plasma.keyboardindicator.po | 67 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 30 + ...plasma_applet_org.kde.plasma.mediaframe.po | 217 ++ po/it/plasma_applet_org.kde.plasma.notes.po | 312 ++ ..._applet_org.kde.plasma.private.grouping.po | 23 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 132 + po/it/plasma_applet_org.kde.plasma.timer.po | 256 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 154 + po/it/plasma_applet_org.kde.plasma.weather.po | 746 ++++ ...plasma_applet_org.kde.plasma.webbrowser.po | 192 + ...lasma_applet_org.kde.plasma_applet_dict.po | 70 + po/it/plasma_calendar_alternatecalendar.po | 193 ++ po/it/plasma_calendar_astronomicalevents.po | 35 + po/it/plasma_runner_CharacterRunner.po | 81 + po/it/plasma_runner_converterrunner.po | 603 ++++ po/it/plasma_runner_datetime.po | 176 + po/it/plasma_runner_katesessions.po | 32 + po/it/plasma_runner_konsoleprofiles.po | 28 + po/it/plasma_runner_krunner_dictionary.po | 48 + po/it/plasma_runner_spellcheckrunner.po | 94 + po/it/plasma_wallpaper_org.kde.potd.po | 329 ++ po/ja/kwin_effect_cube.po | 42 + po/ja/plasma_addons_engine_dict.po | 20 + po/ja/plasma_addons_profiles_utility.po | 25 + ...plet_org.kde.plasma.addons.katesessions.po | 44 + ...lasma_applet_org.kde.plasma.binaryclock.po | 54 + ...plasma_applet_org.kde.plasma.calculator.po | 75 + ...lasma_applet_org.kde.plasma.colorpicker.po | 154 + po/ja/plasma_applet_org.kde.plasma.comic.po | 247 ++ .../plasma_applet_org.kde.plasma.diskquota.po | 87 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 109 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 882 +++++ ...applet_org.kde.plasma.keyboardindicator.po | 59 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 27 + ...plasma_applet_org.kde.plasma.mediaframe.po | 198 ++ po/ja/plasma_applet_org.kde.plasma.notes.po | 239 ++ ..._applet_org.kde.plasma.private.grouping.po | 21 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 124 + po/ja/plasma_applet_org.kde.plasma.timer.po | 213 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 132 + po/ja/plasma_applet_org.kde.plasma.weather.po | 577 +++ ...plasma_applet_org.kde.plasma.webbrowser.po | 185 + ...lasma_applet_org.kde.plasma_applet_dict.po | 76 + po/ja/plasma_calendar_alternatecalendar.po | 137 + po/ja/plasma_calendar_astronomicalevents.po | 34 + po/ja/plasma_runner_CharacterRunner.po | 81 + po/ja/plasma_runner_converterrunner.po | 44 + po/ja/plasma_runner_datetime.po | 158 + po/ja/plasma_runner_katesessions.po | 36 + po/ja/plasma_runner_konsoleprofiles.po | 26 + po/ja/plasma_runner_krunner_dictionary.po | 45 + po/ja/plasma_runner_spellcheckrunner.po | 81 + po/ja/plasma_wallpaper_org.kde.potd.po | 213 ++ po/ka/kwin_effect_cube.po | 51 + po/ka/plasma_addons_engine_dict.po | 24 + po/ka/plasma_addons_profiles_utility.po | 29 + ...plet_org.kde.plasma.addons.katesessions.po | 53 + ...lasma_applet_org.kde.plasma.binaryclock.po | 60 + ...plasma_applet_org.kde.plasma.calculator.po | 75 + ...lasma_applet_org.kde.plasma.colorpicker.po | 154 + po/ka/plasma_applet_org.kde.plasma.comic.po | 256 ++ .../plasma_applet_org.kde.plasma.diskquota.po | 81 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 107 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 881 +++++ ...applet_org.kde.plasma.keyboardindicator.po | 59 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 30 + ...plasma_applet_org.kde.plasma.mediaframe.po | 204 ++ po/ka/plasma_applet_org.kde.plasma.notes.po | 233 ++ ..._applet_org.kde.plasma.private.grouping.po | 24 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 123 + po/ka/plasma_applet_org.kde.plasma.timer.po | 213 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 139 + po/ka/plasma_applet_org.kde.plasma.weather.po | 606 ++++ ...plasma_applet_org.kde.plasma.webbrowser.po | 191 + ...lasma_applet_org.kde.plasma_applet_dict.po | 68 + po/ka/plasma_calendar_alternatecalendar.po | 192 + po/ka/plasma_calendar_astronomicalevents.po | 36 + po/ka/plasma_runner_CharacterRunner.po | 84 + po/ka/plasma_runner_converterrunner.po | 40 + po/ka/plasma_runner_datetime.po | 171 + po/ka/plasma_runner_katesessions.po | 34 + po/ka/plasma_runner_konsoleprofiles.po | 29 + po/ka/plasma_runner_krunner_dictionary.po | 48 + po/ka/plasma_runner_spellcheckrunner.po | 84 + po/ka/plasma_wallpaper_org.kde.potd.po | 326 ++ ...lasma_applet_org.kde.plasma.binaryclock.po | 112 + ...plasma_applet_org.kde.plasma.calculator.po | 95 + ...lasma_applet_org.kde.plasma.colorpicker.po | 164 + po/kk/plasma_applet_org.kde.plasma.comic.po | 486 +++ ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 160 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 1305 +++++++ ...a_applet_org.kde.plasma.konsoleprofiles.po | 30 + po/kk/plasma_applet_org.kde.plasma.notes.po | 312 ++ po/kk/plasma_applet_org.kde.plasma.timer.po | 250 ++ po/kk/plasma_applet_org.kde.plasma.weather.po | 751 ++++ po/kk/plasma_runner_CharacterRunner.po | 83 + po/kk/plasma_runner_converterrunner.po | 43 + po/kk/plasma_runner_datetime.po | 166 + po/kk/plasma_runner_katesessions.po | 33 + po/kk/plasma_runner_krunner_dictionary.po | 48 + po/kk/plasma_runner_spellcheckrunner.po | 91 + ...lasma_applet_org.kde.plasma.binaryclock.po | 116 + ...plasma_applet_org.kde.plasma.calculator.po | 108 + ...lasma_applet_org.kde.plasma.colorpicker.po | 165 + po/km/plasma_applet_org.kde.plasma.comic.po | 482 +++ ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 160 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 1322 +++++++ ...a_applet_org.kde.plasma.konsoleprofiles.po | 31 + po/km/plasma_applet_org.kde.plasma.notes.po | 317 ++ po/km/plasma_applet_org.kde.plasma.timer.po | 251 ++ po/km/plasma_applet_org.kde.plasma.weather.po | 756 ++++ po/km/plasma_runner_CharacterRunner.po | 82 + po/km/plasma_runner_converterrunner.po | 44 + po/km/plasma_runner_datetime.po | 167 + po/km/plasma_runner_katesessions.po | 35 + po/km/plasma_runner_krunner_dictionary.po | 51 + po/km/plasma_runner_spellcheckrunner.po | 91 + po/ko/kwin_effect_cube.po | 46 + po/ko/plasma_addons_engine_dict.po | 23 + po/ko/plasma_addons_profiles_utility.po | 28 + ...plet_org.kde.plasma.addons.katesessions.po | 55 + ...lasma_applet_org.kde.plasma.binaryclock.po | 118 + ...plasma_applet_org.kde.plasma.calculator.po | 74 + ...lasma_applet_org.kde.plasma.colorpicker.po | 159 + po/ko/plasma_applet_org.kde.plasma.comic.po | 388 +++ .../plasma_applet_org.kde.plasma.diskquota.po | 80 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 148 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 1051 ++++++ ...applet_org.kde.plasma.keyboardindicator.po | 67 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 29 + ...plasma_applet_org.kde.plasma.mediaframe.po | 215 ++ po/ko/plasma_applet_org.kde.plasma.notes.po | 304 ++ ..._applet_org.kde.plasma.private.grouping.po | 23 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 130 + po/ko/plasma_applet_org.kde.plasma.timer.po | 250 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 156 + po/ko/plasma_applet_org.kde.plasma.weather.po | 744 ++++ ...plasma_applet_org.kde.plasma.webbrowser.po | 192 + ...lasma_applet_org.kde.plasma_applet_dict.po | 70 + po/ko/plasma_calendar_alternatecalendar.po | 141 + po/ko/plasma_calendar_astronomicalevents.po | 35 + po/ko/plasma_runner_CharacterRunner.po | 81 + po/ko/plasma_runner_converterrunner.po | 38 + po/ko/plasma_runner_datetime.po | 166 + po/ko/plasma_runner_katesessions.po | 33 + po/ko/plasma_runner_konsoleprofiles.po | 28 + po/ko/plasma_runner_krunner_dictionary.po | 47 + po/ko/plasma_runner_spellcheckrunner.po | 94 + po/ko/plasma_wallpaper_org.kde.potd.po | 328 ++ ...lasma_applet_org.kde.plasma.binaryclock.po | 136 + ...plasma_applet_org.kde.plasma.calculator.po | 106 + ...lasma_applet_org.kde.plasma.colorpicker.po | 153 + po/ku/plasma_applet_org.kde.plasma.comic.po | 298 ++ ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 152 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 1313 +++++++ po/ku/plasma_applet_org.kde.plasma.notes.po | 310 ++ po/ku/plasma_applet_org.kde.plasma.timer.po | 250 ++ po/ku/plasma_applet_org.kde.plasma.weather.po | 941 +++++ po/ku/plasma_runner_converterrunner.po | 602 ++++ po/ku/plasma_runner_katesessions.po | 35 + po/ku/plasma_runner_spellcheckrunner.po | 82 + po/lt/kwin_effect_cube.po | 54 + po/lt/plasma_addons_engine_dict.po | 25 + po/lt/plasma_addons_profiles_utility.po | 30 + ...plet_org.kde.plasma.addons.katesessions.po | 58 + ...lasma_applet_org.kde.plasma.binaryclock.po | 118 + ...plasma_applet_org.kde.plasma.calculator.po | 77 + ...lasma_applet_org.kde.plasma.colorpicker.po | 163 + po/lt/plasma_applet_org.kde.plasma.comic.po | 381 ++ .../plasma_applet_org.kde.plasma.diskquota.po | 82 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 139 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 889 +++++ ...applet_org.kde.plasma.keyboardindicator.po | 69 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 32 + ...plasma_applet_org.kde.plasma.mediaframe.po | 207 ++ po/lt/plasma_applet_org.kde.plasma.notes.po | 235 ++ ..._applet_org.kde.plasma.private.grouping.po | 25 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 124 + po/lt/plasma_applet_org.kde.plasma.timer.po | 243 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 142 + po/lt/plasma_applet_org.kde.plasma.weather.po | 760 ++++ ...plasma_applet_org.kde.plasma.webbrowser.po | 194 ++ ...lasma_applet_org.kde.plasma_applet_dict.po | 72 + po/lt/plasma_calendar_alternatecalendar.po | 149 + po/lt/plasma_calendar_astronomicalevents.po | 37 + po/lt/plasma_runner_CharacterRunner.po | 87 + po/lt/plasma_runner_converterrunner.po | 44 + po/lt/plasma_runner_datetime.po | 185 + po/lt/plasma_runner_katesessions.po | 35 + po/lt/plasma_runner_konsoleprofiles.po | 30 + po/lt/plasma_runner_krunner_dictionary.po | 51 + po/lt/plasma_runner_spellcheckrunner.po | 97 + po/lt/plasma_wallpaper_org.kde.potd.po | 332 ++ po/lv/kwin_effect_cube.po | 54 + po/lv/plasma_addons_engine_dict.po | 24 + po/lv/plasma_addons_profiles_utility.po | 29 + ...plet_org.kde.plasma.addons.katesessions.po | 44 + ...lasma_applet_org.kde.plasma.binaryclock.po | 157 + ...plasma_applet_org.kde.plasma.calculator.po | 88 + ...lasma_applet_org.kde.plasma.colorpicker.po | 152 + po/lv/plasma_applet_org.kde.plasma.comic.po | 464 +++ .../plasma_applet_org.kde.plasma.diskquota.po | 81 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 157 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 1051 ++++++ ...applet_org.kde.plasma.keyboardindicator.po | 69 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 30 + ...plasma_applet_org.kde.plasma.mediaframe.po | 202 ++ po/lv/plasma_applet_org.kde.plasma.notes.po | 312 ++ ..._applet_org.kde.plasma.private.grouping.po | 24 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 124 + po/lv/plasma_applet_org.kde.plasma.timer.po | 251 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 137 + po/lv/plasma_applet_org.kde.plasma.weather.po | 810 +++++ ...plasma_applet_org.kde.plasma.webbrowser.po | 193 ++ ...lasma_applet_org.kde.plasma_applet_dict.po | 68 + po/lv/plasma_calendar_alternatecalendar.po | 147 + po/lv/plasma_calendar_astronomicalevents.po | 36 + po/lv/plasma_runner_CharacterRunner.po | 86 + po/lv/plasma_runner_converterrunner.po | 550 +++ po/lv/plasma_runner_datetime.po | 171 + po/lv/plasma_runner_katesessions.po | 35 + po/lv/plasma_runner_konsoleprofiles.po | 29 + po/lv/plasma_runner_krunner_dictionary.po | 48 + po/lv/plasma_runner_spellcheckrunner.po | 81 + po/lv/plasma_wallpaper_org.kde.potd.po | 215 ++ po/ml/plasma_addons_engine_dict.po | 25 + ...plet_org.kde.plasma.addons.katesessions.po | 44 + ...lasma_applet_org.kde.plasma.binaryclock.po | 49 + ...plasma_applet_org.kde.plasma.calculator.po | 75 + ...lasma_applet_org.kde.plasma.colorpicker.po | 148 + po/ml/plasma_applet_org.kde.plasma.comic.po | 246 ++ .../plasma_applet_org.kde.plasma.diskquota.po | 81 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 107 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 881 +++++ ...applet_org.kde.plasma.keyboardindicator.po | 59 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 30 + ...plasma_applet_org.kde.plasma.mediaframe.po | 200 ++ po/ml/plasma_applet_org.kde.plasma.notes.po | 230 ++ ..._applet_org.kde.plasma.private.grouping.po | 24 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 123 + po/ml/plasma_applet_org.kde.plasma.timer.po | 206 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 131 + po/ml/plasma_applet_org.kde.plasma.weather.po | 553 +++ ...lasma_applet_org.kde.plasma_applet_dict.po | 68 + po/ml/plasma_calendar_astronomicalevents.po | 36 + po/ml/plasma_runner_CharacterRunner.po | 81 + po/ml/plasma_runner_converterrunner.po | 37 + po/ml/plasma_runner_datetime.po | 158 + po/ml/plasma_runner_katesessions.po | 34 + po/ml/plasma_runner_konsoleprofiles.po | 29 + po/ml/plasma_runner_krunner_dictionary.po | 48 + po/ml/plasma_runner_spellcheckrunner.po | 79 + ...lasma_applet_org.kde.plasma.binaryclock.po | 87 + ...plasma_applet_org.kde.plasma.calculator.po | 74 + ...lasma_applet_org.kde.plasma.colorpicker.po | 162 + po/mr/plasma_applet_org.kde.plasma.comic.po | 304 ++ ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 141 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 1196 +++++++ ...a_applet_org.kde.plasma.konsoleprofiles.po | 30 + po/mr/plasma_applet_org.kde.plasma.notes.po | 309 ++ po/mr/plasma_applet_org.kde.plasma.timer.po | 253 ++ po/mr/plasma_applet_org.kde.plasma.weather.po | 725 ++++ po/mr/plasma_runner_CharacterRunner.po | 81 + po/mr/plasma_runner_converterrunner.po | 36 + po/mr/plasma_runner_datetime.po | 166 + po/mr/plasma_runner_katesessions.po | 33 + po/mr/plasma_runner_krunner_dictionary.po | 48 + po/mr/plasma_runner_spellcheckrunner.po | 90 + ...lasma_applet_org.kde.plasma.binaryclock.po | 66 + ...plasma_applet_org.kde.plasma.calculator.po | 93 + po/ms/plasma_applet_org.kde.plasma.comic.po | 272 ++ ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 120 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 930 +++++ ...plet_org.kde.plasma.addons.katesessions.po | 53 + po/nb/plasma_addons_engine_dict.po | 26 + ...plet_org.kde.plasma.addons.katesessions.po | 46 + ...lasma_applet_org.kde.plasma.binaryclock.po | 50 + ...plasma_applet_org.kde.plasma.calculator.po | 76 + ...lasma_applet_org.kde.plasma.colorpicker.po | 149 + po/nb/plasma_applet_org.kde.plasma.comic.po | 247 ++ ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 108 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 884 +++++ ...a_applet_org.kde.plasma.konsoleprofiles.po | 32 + po/nb/plasma_applet_org.kde.plasma.notes.po | 231 ++ po/nb/plasma_applet_org.kde.plasma.timer.po | 207 ++ po/nb/plasma_applet_org.kde.plasma.weather.po | 554 +++ po/nb/plasma_calendar_astronomicalevents.po | 37 + po/nb/plasma_runner_CharacterRunner.po | 84 + po/nb/plasma_runner_converterrunner.po | 41 + po/nb/plasma_runner_datetime.po | 159 + po/nb/plasma_runner_katesessions.po | 35 + po/nb/plasma_runner_konsoleprofiles.po | 30 + po/nb/plasma_runner_krunner_dictionary.po | 50 + po/nb/plasma_runner_spellcheckrunner.po | 80 + ...plet_org.kde.plasma.addons.katesessions.po | 58 + ...lasma_applet_org.kde.plasma.binaryclock.po | 160 + ...plasma_applet_org.kde.plasma.calculator.po | 108 + ...lasma_applet_org.kde.plasma.colorpicker.po | 166 + po/nds/plasma_applet_org.kde.plasma.comic.po | 549 +++ ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 176 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 1341 +++++++ ...a_applet_org.kde.plasma.konsoleprofiles.po | 31 + po/nds/plasma_applet_org.kde.plasma.notes.po | 350 ++ po/nds/plasma_applet_org.kde.plasma.timer.po | 261 ++ .../plasma_applet_org.kde.plasma.weather.po | 958 +++++ po/nds/plasma_runner_CharacterRunner.po | 84 + po/nds/plasma_runner_converterrunner.po | 604 ++++ po/nds/plasma_runner_datetime.po | 167 + po/nds/plasma_runner_katesessions.po | 37 + po/nds/plasma_runner_krunner_dictionary.po | 49 + po/nds/plasma_runner_spellcheckrunner.po | 94 + po/nl/kwin_effect_cube.po | 50 + po/nl/plasma_addons_engine_dict.po | 23 + po/nl/plasma_addons_profiles_utility.po | 28 + ...plet_org.kde.plasma.addons.katesessions.po | 58 + ...lasma_applet_org.kde.plasma.binaryclock.po | 83 + ...plasma_applet_org.kde.plasma.calculator.po | 74 + ...lasma_applet_org.kde.plasma.colorpicker.po | 155 + po/nl/plasma_applet_org.kde.plasma.comic.po | 488 +++ .../plasma_applet_org.kde.plasma.diskquota.po | 80 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 160 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 930 +++++ ...applet_org.kde.plasma.keyboardindicator.po | 70 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 30 + ...plasma_applet_org.kde.plasma.mediaframe.po | 217 ++ po/nl/plasma_applet_org.kde.plasma.notes.po | 316 ++ ..._applet_org.kde.plasma.private.grouping.po | 23 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 132 + po/nl/plasma_applet_org.kde.plasma.timer.po | 235 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 154 + po/nl/plasma_applet_org.kde.plasma.weather.po | 752 ++++ ...plasma_applet_org.kde.plasma.webbrowser.po | 192 + ...lasma_applet_org.kde.plasma_applet_dict.po | 70 + po/nl/plasma_calendar_alternatecalendar.po | 193 ++ po/nl/plasma_calendar_astronomicalevents.po | 35 + po/nl/plasma_runner_CharacterRunner.po | 84 + po/nl/plasma_runner_converterrunner.po | 606 ++++ po/nl/plasma_runner_datetime.po | 176 + po/nl/plasma_runner_katesessions.po | 34 + po/nl/plasma_runner_konsoleprofiles.po | 28 + po/nl/plasma_runner_krunner_dictionary.po | 47 + po/nl/plasma_runner_spellcheckrunner.po | 101 + po/nl/plasma_wallpaper_org.kde.potd.po | 325 ++ po/nn/kwin_effect_cube.po | 51 + po/nn/plasma_addons_engine_dict.po | 25 + po/nn/plasma_addons_profiles_utility.po | 30 + ...plet_org.kde.plasma.addons.katesessions.po | 46 + ...lasma_applet_org.kde.plasma.binaryclock.po | 51 + ...plasma_applet_org.kde.plasma.calculator.po | 77 + ...lasma_applet_org.kde.plasma.colorpicker.po | 155 + po/nn/plasma_applet_org.kde.plasma.comic.po | 249 ++ .../plasma_applet_org.kde.plasma.diskquota.po | 82 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 110 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 885 +++++ ...applet_org.kde.plasma.keyboardindicator.po | 61 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 32 + ...plasma_applet_org.kde.plasma.mediaframe.po | 202 ++ po/nn/plasma_applet_org.kde.plasma.notes.po | 232 ++ ..._applet_org.kde.plasma.private.grouping.po | 25 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 125 + po/nn/plasma_applet_org.kde.plasma.timer.po | 213 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 132 + po/nn/plasma_applet_org.kde.plasma.weather.po | 562 +++ ...plasma_applet_org.kde.plasma.webbrowser.po | 193 ++ ...lasma_applet_org.kde.plasma_applet_dict.po | 70 + po/nn/plasma_calendar_alternatecalendar.po | 147 + po/nn/plasma_calendar_astronomicalevents.po | 37 + po/nn/plasma_runner_CharacterRunner.po | 87 + po/nn/plasma_runner_converterrunner.po | 43 + po/nn/plasma_runner_datetime.po | 169 + po/nn/plasma_runner_katesessions.po | 35 + po/nn/plasma_runner_konsoleprofiles.po | 30 + po/nn/plasma_runner_krunner_dictionary.po | 50 + po/nn/plasma_runner_spellcheckrunner.po | 84 + po/nn/plasma_wallpaper_org.kde.potd.po | 211 ++ po/pa/plasma_addons_engine_dict.po | 23 + ...plet_org.kde.plasma.addons.katesessions.po | 44 + ...lasma_applet_org.kde.plasma.binaryclock.po | 125 + ...plasma_applet_org.kde.plasma.calculator.po | 96 + ...lasma_applet_org.kde.plasma.colorpicker.po | 157 + po/pa/plasma_applet_org.kde.plasma.comic.po | 404 +++ ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 158 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 1295 +++++++ ...applet_org.kde.plasma.keyboardindicator.po | 67 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 29 + po/pa/plasma_applet_org.kde.plasma.notes.po | 319 ++ ...lasma_applet_org.kde.plasma.quicklaunch.po | 134 + po/pa/plasma_applet_org.kde.plasma.timer.po | 247 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 159 + po/pa/plasma_applet_org.kde.plasma.weather.po | 844 +++++ po/pa/plasma_runner_CharacterRunner.po | 81 + po/pa/plasma_runner_converterrunner.po | 549 +++ po/pa/plasma_runner_datetime.po | 163 + po/pa/plasma_runner_katesessions.po | 34 + po/pa/plasma_runner_krunner_dictionary.po | 47 + po/pa/plasma_runner_spellcheckrunner.po | 81 + po/pl/kwin_effect_cube.po | 53 + po/pl/plasma_addons_engine_dict.po | 23 + po/pl/plasma_addons_profiles_utility.po | 28 + ...plet_org.kde.plasma.addons.katesessions.po | 55 + ...lasma_applet_org.kde.plasma.binaryclock.po | 146 + ...plasma_applet_org.kde.plasma.calculator.po | 74 + ...lasma_applet_org.kde.plasma.colorpicker.po | 163 + po/pl/plasma_applet_org.kde.plasma.comic.po | 452 +++ .../plasma_applet_org.kde.plasma.diskquota.po | 80 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 163 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 932 +++++ ...applet_org.kde.plasma.keyboardindicator.po | 71 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 29 + ...plasma_applet_org.kde.plasma.mediaframe.po | 219 ++ po/pl/plasma_applet_org.kde.plasma.notes.po | 314 ++ ..._applet_org.kde.plasma.private.grouping.po | 23 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 132 + po/pl/plasma_applet_org.kde.plasma.timer.po | 237 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 155 + po/pl/plasma_applet_org.kde.plasma.weather.po | 751 ++++ ...plasma_applet_org.kde.plasma.webbrowser.po | 193 ++ ...lasma_applet_org.kde.plasma_applet_dict.po | 70 + po/pl/plasma_calendar_alternatecalendar.po | 192 + po/pl/plasma_calendar_astronomicalevents.po | 35 + po/pl/plasma_runner_CharacterRunner.po | 86 + po/pl/plasma_runner_converterrunner.po | 606 ++++ po/pl/plasma_runner_datetime.po | 176 + po/pl/plasma_runner_katesessions.po | 35 + po/pl/plasma_runner_konsoleprofiles.po | 28 + po/pl/plasma_runner_krunner_dictionary.po | 47 + po/pl/plasma_runner_spellcheckrunner.po | 98 + po/pl/plasma_wallpaper_org.kde.potd.po | 326 ++ po/pt/plasma_addons_engine_dict.po | 23 + po/pt/plasma_addons_profiles_utility.po | 24 + ...plet_org.kde.plasma.addons.katesessions.po | 54 + ...lasma_applet_org.kde.plasma.binaryclock.po | 59 + ...plasma_applet_org.kde.plasma.calculator.po | 70 + ...lasma_applet_org.kde.plasma.colorpicker.po | 144 + po/pt/plasma_applet_org.kde.plasma.comic.po | 333 ++ .../plasma_applet_org.kde.plasma.diskquota.po | 75 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 102 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 951 +++++ ...applet_org.kde.plasma.keyboardindicator.po | 56 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 25 + ...plasma_applet_org.kde.plasma.mediaframe.po | 206 ++ po/pt/plasma_applet_org.kde.plasma.notes.po | 234 ++ ..._applet_org.kde.plasma.private.grouping.po | 18 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 117 + po/pt/plasma_applet_org.kde.plasma.timer.po | 209 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 137 + po/pt/plasma_applet_org.kde.plasma.weather.po | 574 +++ ...plasma_applet_org.kde.plasma.webbrowser.po | 189 + ...lasma_applet_org.kde.plasma_applet_dict.po | 67 + po/pt/plasma_calendar_alternatecalendar.po | 146 + po/pt/plasma_calendar_astronomicalevents.po | 30 + po/pt/plasma_runner_CharacterRunner.po | 80 + po/pt/plasma_runner_converterrunner.po | 44 + po/pt/plasma_runner_datetime.po | 164 + po/pt/plasma_runner_katesessions.po | 29 + po/pt/plasma_runner_konsoleprofiles.po | 24 + po/pt/plasma_runner_krunner_dictionary.po | 47 + po/pt/plasma_runner_spellcheckrunner.po | 82 + po/pt/plasma_wallpaper_org.kde.potd.po | 326 ++ po/pt_BR/kwin_effect_cube.po | 51 + po/pt_BR/plasma_addons_engine_dict.po | 23 + po/pt_BR/plasma_addons_profiles_utility.po | 28 + ...plet_org.kde.plasma.addons.katesessions.po | 57 + ...lasma_applet_org.kde.plasma.binaryclock.po | 123 + ...plasma_applet_org.kde.plasma.calculator.po | 76 + ...lasma_applet_org.kde.plasma.colorpicker.po | 158 + .../plasma_applet_org.kde.plasma.comic.po | 344 ++ .../plasma_applet_org.kde.plasma.diskquota.po | 82 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 108 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 888 +++++ ...applet_org.kde.plasma.keyboardindicator.po | 67 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 30 + ...plasma_applet_org.kde.plasma.mediaframe.po | 208 ++ .../plasma_applet_org.kde.plasma.notes.po | 252 ++ ..._applet_org.kde.plasma.private.grouping.po | 23 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 125 + .../plasma_applet_org.kde.plasma.timer.po | 215 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 141 + .../plasma_applet_org.kde.plasma.weather.po | 677 ++++ ...plasma_applet_org.kde.plasma.webbrowser.po | 193 ++ ...lasma_applet_org.kde.plasma_applet_dict.po | 70 + po/pt_BR/plasma_calendar_alternatecalendar.po | 191 + .../plasma_calendar_astronomicalevents.po | 37 + po/pt_BR/plasma_runner_CharacterRunner.po | 86 + po/pt_BR/plasma_runner_converterrunner.po | 44 + po/pt_BR/plasma_runner_datetime.po | 176 + po/pt_BR/plasma_runner_katesessions.po | 35 + po/pt_BR/plasma_runner_konsoleprofiles.po | 28 + po/pt_BR/plasma_runner_krunner_dictionary.po | 48 + po/pt_BR/plasma_runner_spellcheckrunner.po | 93 + po/pt_BR/plasma_wallpaper_org.kde.potd.po | 322 ++ po/ro/kwin_effect_cube.po | 54 + po/ro/plasma_addons_engine_dict.po | 24 + po/ro/plasma_addons_profiles_utility.po | 29 + ...plet_org.kde.plasma.addons.katesessions.po | 56 + ...lasma_applet_org.kde.plasma.binaryclock.po | 158 + ...plasma_applet_org.kde.plasma.calculator.po | 99 + ...lasma_applet_org.kde.plasma.colorpicker.po | 165 + po/ro/plasma_applet_org.kde.plasma.comic.po | 356 ++ .../plasma_applet_org.kde.plasma.diskquota.po | 81 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 156 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 1076 ++++++ ...applet_org.kde.plasma.keyboardindicator.po | 68 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 30 + ...plasma_applet_org.kde.plasma.mediaframe.po | 204 ++ po/ro/plasma_applet_org.kde.plasma.notes.po | 319 ++ ..._applet_org.kde.plasma.private.grouping.po | 24 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 124 + po/ro/plasma_applet_org.kde.plasma.timer.po | 252 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 141 + po/ro/plasma_applet_org.kde.plasma.weather.po | 910 +++++ ...plasma_applet_org.kde.plasma.webbrowser.po | 197 ++ ...lasma_applet_org.kde.plasma_applet_dict.po | 71 + po/ro/plasma_calendar_alternatecalendar.po | 147 + po/ro/plasma_calendar_astronomicalevents.po | 36 + po/ro/plasma_runner_CharacterRunner.po | 84 + po/ro/plasma_runner_converterrunner.po | 496 +++ po/ro/plasma_runner_datetime.po | 170 + po/ro/plasma_runner_katesessions.po | 33 + po/ro/plasma_runner_konsoleprofiles.po | 29 + po/ro/plasma_runner_krunner_dictionary.po | 48 + po/ro/plasma_runner_spellcheckrunner.po | 93 + po/ro/plasma_wallpaper_org.kde.potd.po | 327 ++ po/ru/kwin_effect_cube.po | 58 + po/ru/plasma_addons_engine_dict.po | 24 + po/ru/plasma_addons_profiles_utility.po | 29 + ...plet_org.kde.plasma.addons.katesessions.po | 61 + ...lasma_applet_org.kde.plasma.binaryclock.po | 148 + ...plasma_applet_org.kde.plasma.calculator.po | 107 + ...lasma_applet_org.kde.plasma.colorpicker.po | 167 + po/ru/plasma_applet_org.kde.plasma.comic.po | 459 +++ .../plasma_applet_org.kde.plasma.diskquota.po | 82 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 165 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 1050 ++++++ ...applet_org.kde.plasma.keyboardindicator.po | 69 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 31 + ...plasma_applet_org.kde.plasma.mediaframe.po | 225 ++ po/ru/plasma_applet_org.kde.plasma.notes.po | 324 ++ ..._applet_org.kde.plasma.private.grouping.po | 24 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 195 ++ po/ru/plasma_applet_org.kde.plasma.timer.po | 272 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 160 + po/ru/plasma_applet_org.kde.plasma.weather.po | 816 +++++ ...plasma_applet_org.kde.plasma.webbrowser.po | 194 ++ ...lasma_applet_org.kde.plasma_applet_dict.po | 72 + po/ru/plasma_calendar_alternatecalendar.po | 196 ++ po/ru/plasma_calendar_astronomicalevents.po | 37 + po/ru/plasma_runner_CharacterRunner.po | 87 + po/ru/plasma_runner_converterrunner.po | 47 + po/ru/plasma_runner_datetime.po | 182 + po/ru/plasma_runner_katesessions.po | 38 + po/ru/plasma_runner_konsoleprofiles.po | 29 + po/ru/plasma_runner_krunner_dictionary.po | 54 + po/ru/plasma_runner_spellcheckrunner.po | 98 + po/ru/plasma_wallpaper_org.kde.potd.po | 327 ++ po/sk/kwin_effect_cube.po | 45 + po/sk/plasma_addons_engine_dict.po | 21 + po/sk/plasma_addons_profiles_utility.po | 26 + ...plet_org.kde.plasma.addons.katesessions.po | 54 + ...lasma_applet_org.kde.plasma.binaryclock.po | 120 + ...plasma_applet_org.kde.plasma.calculator.po | 73 + ...lasma_applet_org.kde.plasma.colorpicker.po | 161 + po/sk/plasma_applet_org.kde.plasma.comic.po | 399 +++ .../plasma_applet_org.kde.plasma.diskquota.po | 79 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 150 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 932 +++++ ...applet_org.kde.plasma.keyboardindicator.po | 67 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 29 + ...plasma_applet_org.kde.plasma.mediaframe.po | 214 ++ po/sk/plasma_applet_org.kde.plasma.notes.po | 313 ++ ..._applet_org.kde.plasma.private.grouping.po | 21 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 130 + po/sk/plasma_applet_org.kde.plasma.timer.po | 238 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 156 + po/sk/plasma_applet_org.kde.plasma.weather.po | 753 ++++ ...plasma_applet_org.kde.plasma.webbrowser.po | 195 ++ ...lasma_applet_org.kde.plasma_applet_dict.po | 70 + po/sk/plasma_calendar_alternatecalendar.po | 144 + po/sk/plasma_calendar_astronomicalevents.po | 34 + po/sk/plasma_runner_CharacterRunner.po | 84 + po/sk/plasma_runner_converterrunner.po | 46 + po/sk/plasma_runner_datetime.po | 170 + po/sk/plasma_runner_katesessions.po | 32 + po/sk/plasma_runner_konsoleprofiles.po | 26 + po/sk/plasma_runner_krunner_dictionary.po | 47 + po/sk/plasma_runner_spellcheckrunner.po | 97 + po/sk/plasma_wallpaper_org.kde.potd.po | 330 ++ po/sl/kwin_effect_cube.po | 57 + po/sl/plasma_addons_engine_dict.po | 25 + po/sl/plasma_addons_profiles_utility.po | 29 + ...plet_org.kde.plasma.addons.katesessions.po | 57 + ...lasma_applet_org.kde.plasma.binaryclock.po | 135 + ...plasma_applet_org.kde.plasma.calculator.po | 78 + ...lasma_applet_org.kde.plasma.colorpicker.po | 162 + po/sl/plasma_applet_org.kde.plasma.comic.po | 411 +++ .../plasma_applet_org.kde.plasma.diskquota.po | 82 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 167 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 1054 ++++++ ...applet_org.kde.plasma.keyboardindicator.po | 68 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 32 + ...plasma_applet_org.kde.plasma.mediaframe.po | 225 ++ po/sl/plasma_applet_org.kde.plasma.notes.po | 311 ++ ..._applet_org.kde.plasma.private.grouping.po | 24 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 138 + po/sl/plasma_applet_org.kde.plasma.timer.po | 270 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 165 + po/sl/plasma_applet_org.kde.plasma.weather.po | 799 +++++ ...plasma_applet_org.kde.plasma.webbrowser.po | 193 ++ ...lasma_applet_org.kde.plasma_applet_dict.po | 73 + po/sl/plasma_calendar_alternatecalendar.po | 196 ++ po/sl/plasma_calendar_astronomicalevents.po | 36 + po/sl/plasma_runner_CharacterRunner.po | 87 + po/sl/plasma_runner_converterrunner.po | 47 + po/sl/plasma_runner_datetime.po | 177 + po/sl/plasma_runner_katesessions.po | 36 + po/sl/plasma_runner_konsoleprofiles.po | 29 + po/sl/plasma_runner_krunner_dictionary.po | 51 + po/sl/plasma_runner_spellcheckrunner.po | 99 + po/sl/plasma_wallpaper_org.kde.potd.po | 325 ++ ...lasma_applet_org.kde.plasma.binaryclock.po | 112 + ...plasma_applet_org.kde.plasma.calculator.po | 96 + ...lasma_applet_org.kde.plasma.colorpicker.po | 151 + po/sq/plasma_applet_org.kde.plasma.comic.po | 359 ++ ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 138 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 1311 +++++++ po/sq/plasma_applet_org.kde.plasma.notes.po | 314 ++ po/sq/plasma_applet_org.kde.plasma.timer.po | 251 ++ po/sq/plasma_applet_org.kde.plasma.weather.po | 720 ++++ po/sq/plasma_runner_converterrunner.po | 45 + po/sq/plasma_runner_katesessions.po | 35 + po/sq/plasma_runner_spellcheckrunner.po | 82 + po/sr/plasma_addons_engine_dict.po | 24 + ...plet_org.kde.plasma.addons.katesessions.po | 57 + ...lasma_applet_org.kde.plasma.binaryclock.po | 60 + ...plasma_applet_org.kde.plasma.calculator.po | 75 + ...lasma_applet_org.kde.plasma.colorpicker.po | 173 + po/sr/plasma_applet_org.kde.plasma.comic.po | 390 +++ .../plasma_applet_org.kde.plasma.diskquota.po | 94 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 117 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 891 +++++ ...a_applet_org.kde.plasma.konsoleprofiles.po | 31 + ...plasma_applet_org.kde.plasma.mediaframe.po | 248 ++ po/sr/plasma_applet_org.kde.plasma.notes.po | 258 ++ ..._applet_org.kde.plasma.private.grouping.po | 24 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 139 + po/sr/plasma_applet_org.kde.plasma.timer.po | 231 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 146 + po/sr/plasma_applet_org.kde.plasma.weather.po | 711 ++++ ...lasma_applet_org.kde.plasma_applet_dict.po | 76 + po/sr/plasma_runner_CharacterRunner.po | 85 + po/sr/plasma_runner_converterrunner.po | 44 + po/sr/plasma_runner_datetime.po | 161 + po/sr/plasma_runner_katesessions.po | 36 + po/sr/plasma_runner_krunner_dictionary.po | 49 + po/sr/plasma_runner_spellcheckrunner.po | 83 + po/sr@ijekavian/plasma_addons_engine_dict.po | 24 + ...plet_org.kde.plasma.addons.katesessions.po | 57 + ...lasma_applet_org.kde.plasma.binaryclock.po | 60 + ...plasma_applet_org.kde.plasma.calculator.po | 75 + ...lasma_applet_org.kde.plasma.colorpicker.po | 173 + .../plasma_applet_org.kde.plasma.comic.po | 390 +++ .../plasma_applet_org.kde.plasma.diskquota.po | 94 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 117 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 891 +++++ ...a_applet_org.kde.plasma.konsoleprofiles.po | 31 + ...plasma_applet_org.kde.plasma.mediaframe.po | 248 ++ .../plasma_applet_org.kde.plasma.notes.po | 258 ++ ..._applet_org.kde.plasma.private.grouping.po | 24 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 139 + .../plasma_applet_org.kde.plasma.timer.po | 231 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 146 + .../plasma_applet_org.kde.plasma.weather.po | 711 ++++ ...lasma_applet_org.kde.plasma_applet_dict.po | 76 + .../plasma_runner_CharacterRunner.po | 85 + .../plasma_runner_converterrunner.po | 44 + po/sr@ijekavian/plasma_runner_datetime.po | 161 + po/sr@ijekavian/plasma_runner_katesessions.po | 36 + .../plasma_runner_krunner_dictionary.po | 49 + .../plasma_runner_spellcheckrunner.po | 83 + .../plasma_addons_engine_dict.po | 24 + ...plet_org.kde.plasma.addons.katesessions.po | 57 + ...lasma_applet_org.kde.plasma.binaryclock.po | 60 + ...plasma_applet_org.kde.plasma.calculator.po | 75 + ...lasma_applet_org.kde.plasma.colorpicker.po | 173 + .../plasma_applet_org.kde.plasma.comic.po | 390 +++ .../plasma_applet_org.kde.plasma.diskquota.po | 94 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 117 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 891 +++++ ...a_applet_org.kde.plasma.konsoleprofiles.po | 31 + ...plasma_applet_org.kde.plasma.mediaframe.po | 248 ++ .../plasma_applet_org.kde.plasma.notes.po | 258 ++ ..._applet_org.kde.plasma.private.grouping.po | 24 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 139 + .../plasma_applet_org.kde.plasma.timer.po | 231 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 146 + .../plasma_applet_org.kde.plasma.weather.po | 711 ++++ ...lasma_applet_org.kde.plasma_applet_dict.po | 76 + .../plasma_runner_CharacterRunner.po | 85 + .../plasma_runner_converterrunner.po | 44 + .../plasma_runner_datetime.po | 161 + .../plasma_runner_katesessions.po | 36 + .../plasma_runner_krunner_dictionary.po | 49 + .../plasma_runner_spellcheckrunner.po | 83 + po/sr@latin/plasma_addons_engine_dict.po | 24 + ...plet_org.kde.plasma.addons.katesessions.po | 57 + ...lasma_applet_org.kde.plasma.binaryclock.po | 60 + ...plasma_applet_org.kde.plasma.calculator.po | 75 + ...lasma_applet_org.kde.plasma.colorpicker.po | 173 + .../plasma_applet_org.kde.plasma.comic.po | 390 +++ .../plasma_applet_org.kde.plasma.diskquota.po | 94 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 117 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 891 +++++ ...a_applet_org.kde.plasma.konsoleprofiles.po | 31 + ...plasma_applet_org.kde.plasma.mediaframe.po | 248 ++ .../plasma_applet_org.kde.plasma.notes.po | 258 ++ ..._applet_org.kde.plasma.private.grouping.po | 24 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 139 + .../plasma_applet_org.kde.plasma.timer.po | 231 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 146 + .../plasma_applet_org.kde.plasma.weather.po | 711 ++++ ...lasma_applet_org.kde.plasma_applet_dict.po | 76 + po/sr@latin/plasma_runner_CharacterRunner.po | 85 + po/sr@latin/plasma_runner_converterrunner.po | 44 + po/sr@latin/plasma_runner_datetime.po | 161 + po/sr@latin/plasma_runner_katesessions.po | 36 + .../plasma_runner_krunner_dictionary.po | 49 + po/sr@latin/plasma_runner_spellcheckrunner.po | 83 + po/sv/kwin_effect_cube.po | 50 + po/sv/plasma_addons_engine_dict.po | 23 + po/sv/plasma_addons_profiles_utility.po | 28 + ...plet_org.kde.plasma.addons.katesessions.po | 58 + ...lasma_applet_org.kde.plasma.binaryclock.po | 123 + ...plasma_applet_org.kde.plasma.calculator.po | 74 + ...lasma_applet_org.kde.plasma.colorpicker.po | 155 + po/sv/plasma_applet_org.kde.plasma.comic.po | 382 ++ .../plasma_applet_org.kde.plasma.diskquota.po | 80 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 161 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 932 +++++ ...applet_org.kde.plasma.keyboardindicator.po | 70 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 29 + ...plasma_applet_org.kde.plasma.mediaframe.po | 215 ++ po/sv/plasma_applet_org.kde.plasma.notes.po | 312 ++ ..._applet_org.kde.plasma.private.grouping.po | 23 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 132 + po/sv/plasma_applet_org.kde.plasma.timer.po | 235 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 154 + po/sv/plasma_applet_org.kde.plasma.weather.po | 751 ++++ ...plasma_applet_org.kde.plasma.webbrowser.po | 192 + ...lasma_applet_org.kde.plasma_applet_dict.po | 70 + po/sv/plasma_calendar_alternatecalendar.po | 145 + po/sv/plasma_calendar_astronomicalevents.po | 35 + po/sv/plasma_runner_CharacterRunner.po | 84 + po/sv/plasma_runner_converterrunner.po | 55 + po/sv/plasma_runner_datetime.po | 167 + po/sv/plasma_runner_katesessions.po | 34 + po/sv/plasma_runner_konsoleprofiles.po | 28 + po/sv/plasma_runner_krunner_dictionary.po | 47 + po/sv/plasma_runner_spellcheckrunner.po | 96 + po/sv/plasma_wallpaper_org.kde.potd.po | 320 ++ po/ta/kwin_effect_cube.po | 46 + po/ta/plasma_addons_engine_dict.po | 23 + po/ta/plasma_addons_profiles_utility.po | 28 + ...plet_org.kde.plasma.addons.katesessions.po | 52 + ...lasma_applet_org.kde.plasma.binaryclock.po | 48 + ...plasma_applet_org.kde.plasma.calculator.po | 74 + ...lasma_applet_org.kde.plasma.colorpicker.po | 147 + po/ta/plasma_applet_org.kde.plasma.comic.po | 245 ++ .../plasma_applet_org.kde.plasma.diskquota.po | 80 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 106 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 880 +++++ ...applet_org.kde.plasma.keyboardindicator.po | 58 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 29 + ...plasma_applet_org.kde.plasma.mediaframe.po | 203 ++ po/ta/plasma_applet_org.kde.plasma.notes.po | 232 ++ ..._applet_org.kde.plasma.private.grouping.po | 23 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 122 + po/ta/plasma_applet_org.kde.plasma.timer.po | 210 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 138 + po/ta/plasma_applet_org.kde.plasma.weather.po | 593 ++++ ...plasma_applet_org.kde.plasma.webbrowser.po | 189 + ...lasma_applet_org.kde.plasma_applet_dict.po | 67 + po/ta/plasma_calendar_alternatecalendar.po | 194 ++ po/ta/plasma_calendar_astronomicalevents.po | 35 + po/ta/plasma_runner_CharacterRunner.po | 80 + po/ta/plasma_runner_converterrunner.po | 36 + po/ta/plasma_runner_datetime.po | 167 + po/ta/plasma_runner_katesessions.po | 33 + po/ta/plasma_runner_konsoleprofiles.po | 28 + po/ta/plasma_runner_krunner_dictionary.po | 47 + po/ta/plasma_runner_spellcheckrunner.po | 83 + po/ta/plasma_wallpaper_org.kde.potd.po | 206 ++ po/tg/plasma_addons_engine_dict.po | 23 + ...lasma_applet_org.kde.plasma.binaryclock.po | 63 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 29 + po/tg/plasma_applet_org.kde.plasma.notes.po | 237 ++ ..._applet_org.kde.plasma.private.grouping.po | 23 + ...asma_applet_org.kde.plasma.userswitcher.po | 139 + po/tg/plasma_applet_org.kde.plasma.weather.po | 641 ++++ ...lasma_applet_org.kde.plasma_applet_dict.po | 82 + po/tg/plasma_calendar_astronomicalevents.po | 35 + po/tg/plasma_runner_converterrunner.po | 43 + po/tg/plasma_runner_datetime.po | 159 + po/tg/plasma_runner_katesessions.po | 34 + po/tg/plasma_runner_konsoleprofiles.po | 28 + po/tg/plasma_runner_krunner_dictionary.po | 47 + po/tg/plasma_wallpaper_org.kde.potd.po | 246 ++ ...lasma_applet_org.kde.plasma.binaryclock.po | 152 + ...plasma_applet_org.kde.plasma.calculator.po | 105 + ...lasma_applet_org.kde.plasma.colorpicker.po | 152 + po/th/plasma_applet_org.kde.plasma.comic.po | 342 ++ ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 158 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 1309 +++++++ po/th/plasma_applet_org.kde.plasma.notes.po | 362 ++ po/th/plasma_applet_org.kde.plasma.timer.po | 250 ++ po/th/plasma_runner_katesessions.po | 33 + po/th/plasma_runner_krunner_dictionary.po | 50 + po/th/plasma_runner_spellcheckrunner.po | 80 + po/tok/plasma_addons_engine_dict.po | 23 + po/tr/kwin_effect_cube.po | 50 + po/tr/plasma_addons_engine_dict.po | 23 + po/tr/plasma_addons_profiles_utility.po | 28 + ...plet_org.kde.plasma.addons.katesessions.po | 56 + ...lasma_applet_org.kde.plasma.binaryclock.po | 61 + ...plasma_applet_org.kde.plasma.calculator.po | 76 + ...lasma_applet_org.kde.plasma.colorpicker.po | 159 + po/tr/plasma_applet_org.kde.plasma.comic.po | 343 ++ .../plasma_applet_org.kde.plasma.diskquota.po | 81 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 111 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 885 +++++ ...applet_org.kde.plasma.keyboardindicator.po | 58 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 30 + ...plasma_applet_org.kde.plasma.mediaframe.po | 204 ++ po/tr/plasma_applet_org.kde.plasma.notes.po | 236 ++ ..._applet_org.kde.plasma.private.grouping.po | 23 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 124 + po/tr/plasma_applet_org.kde.plasma.timer.po | 212 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 139 + po/tr/plasma_applet_org.kde.plasma.weather.po | 609 ++++ ...plasma_applet_org.kde.plasma.webbrowser.po | 192 + ...lasma_applet_org.kde.plasma_applet_dict.po | 70 + po/tr/plasma_calendar_alternatecalendar.po | 193 ++ po/tr/plasma_calendar_astronomicalevents.po | 35 + po/tr/plasma_runner_CharacterRunner.po | 84 + po/tr/plasma_runner_converterrunner.po | 42 + po/tr/plasma_runner_datetime.po | 171 + po/tr/plasma_runner_katesessions.po | 34 + po/tr/plasma_runner_konsoleprofiles.po | 28 + po/tr/plasma_runner_krunner_dictionary.po | 49 + po/tr/plasma_runner_spellcheckrunner.po | 85 + po/tr/plasma_wallpaper_org.kde.potd.po | 325 ++ ...lasma_applet_org.kde.plasma.binaryclock.po | 80 + ...plasma_applet_org.kde.plasma.calculator.po | 84 + ...lasma_applet_org.kde.plasma.colorpicker.po | 162 + po/ug/plasma_applet_org.kde.plasma.comic.po | 323 ++ ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 116 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 1145 ++++++ ...a_applet_org.kde.plasma.konsoleprofiles.po | 29 + po/ug/plasma_applet_org.kde.plasma.notes.po | 284 ++ po/ug/plasma_applet_org.kde.plasma.timer.po | 218 ++ po/ug/plasma_applet_org.kde.plasma.weather.po | 646 ++++ po/ug/plasma_runner_CharacterRunner.po | 80 + po/ug/plasma_runner_converterrunner.po | 36 + po/ug/plasma_runner_datetime.po | 157 + po/ug/plasma_runner_katesessions.po | 33 + po/ug/plasma_runner_krunner_dictionary.po | 47 + po/ug/plasma_runner_spellcheckrunner.po | 86 + po/uk/kwin_effect_cube.po | 53 + po/uk/plasma_addons_engine_dict.po | 24 + po/uk/plasma_addons_profiles_utility.po | 29 + ...plet_org.kde.plasma.addons.katesessions.po | 61 + ...lasma_applet_org.kde.plasma.binaryclock.po | 86 + ...plasma_applet_org.kde.plasma.calculator.po | 77 + ...lasma_applet_org.kde.plasma.colorpicker.po | 158 + po/uk/plasma_applet_org.kde.plasma.comic.po | 389 +++ .../plasma_applet_org.kde.plasma.diskquota.po | 83 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 137 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 933 +++++ ...applet_org.kde.plasma.keyboardindicator.po | 73 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 32 + ...plasma_applet_org.kde.plasma.mediaframe.po | 221 ++ po/uk/plasma_applet_org.kde.plasma.notes.po | 318 ++ ..._applet_org.kde.plasma.private.grouping.po | 26 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 135 + po/uk/plasma_applet_org.kde.plasma.timer.po | 242 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 157 + po/uk/plasma_applet_org.kde.plasma.weather.po | 762 ++++ ...plasma_applet_org.kde.plasma.webbrowser.po | 193 ++ ...lasma_applet_org.kde.plasma_applet_dict.po | 73 + po/uk/plasma_calendar_alternatecalendar.po | 194 ++ po/uk/plasma_calendar_astronomicalevents.po | 38 + po/uk/plasma_runner_CharacterRunner.po | 87 + po/uk/plasma_runner_converterrunner.po | 45 + po/uk/plasma_runner_datetime.po | 178 + po/uk/plasma_runner_katesessions.po | 40 + po/uk/plasma_runner_konsoleprofiles.po | 31 + po/uk/plasma_runner_krunner_dictionary.po | 50 + po/uk/plasma_runner_spellcheckrunner.po | 98 + po/uk/plasma_wallpaper_org.kde.potd.po | 332 ++ po/vi/kwin_effect_cube.po | 45 + po/vi/plasma_addons_engine_dict.po | 23 + po/vi/plasma_addons_profiles_utility.po | 28 + ...plet_org.kde.plasma.addons.katesessions.po | 55 + ...lasma_applet_org.kde.plasma.binaryclock.po | 64 + ...plasma_applet_org.kde.plasma.calculator.po | 74 + ...lasma_applet_org.kde.plasma.colorpicker.po | 155 + po/vi/plasma_applet_org.kde.plasma.comic.po | 333 ++ .../plasma_applet_org.kde.plasma.diskquota.po | 80 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 106 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 880 +++++ ...applet_org.kde.plasma.keyboardindicator.po | 67 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 29 + ...plasma_applet_org.kde.plasma.mediaframe.po | 203 ++ po/vi/plasma_applet_org.kde.plasma.notes.po | 239 ++ ..._applet_org.kde.plasma.private.grouping.po | 23 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 122 + po/vi/plasma_applet_org.kde.plasma.timer.po | 210 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 140 + po/vi/plasma_applet_org.kde.plasma.weather.po | 608 ++++ ...plasma_applet_org.kde.plasma.webbrowser.po | 194 ++ ...lasma_applet_org.kde.plasma_applet_dict.po | 70 + po/vi/plasma_calendar_alternatecalendar.po | 194 ++ po/vi/plasma_calendar_astronomicalevents.po | 35 + po/vi/plasma_runner_CharacterRunner.po | 84 + po/vi/plasma_runner_converterrunner.po | 42 + po/vi/plasma_runner_datetime.po | 173 + po/vi/plasma_runner_katesessions.po | 34 + po/vi/plasma_runner_konsoleprofiles.po | 28 + po/vi/plasma_runner_krunner_dictionary.po | 47 + po/vi/plasma_runner_spellcheckrunner.po | 87 + po/vi/plasma_wallpaper_org.kde.potd.po | 325 ++ ...lasma_applet_org.kde.plasma.binaryclock.po | 126 + ...plasma_applet_org.kde.plasma.calculator.po | 104 + ...lasma_applet_org.kde.plasma.colorpicker.po | 164 + po/wa/plasma_applet_org.kde.plasma.comic.po | 386 +++ ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 160 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 1319 +++++++ po/wa/plasma_applet_org.kde.plasma.notes.po | 336 ++ po/wa/plasma_applet_org.kde.plasma.timer.po | 252 ++ po/wa/plasma_runner_converterrunner.po | 603 ++++ po/wa/plasma_runner_katesessions.po | 33 + po/wa/plasma_runner_spellcheckrunner.po | 90 + po/zh_CN/kwin_effect_cube.po | 45 + po/zh_CN/plasma_addons_engine_dict.po | 24 + po/zh_CN/plasma_addons_profiles_utility.po | 29 + ...plet_org.kde.plasma.addons.katesessions.po | 44 + ...lasma_applet_org.kde.plasma.binaryclock.po | 49 + ...plasma_applet_org.kde.plasma.calculator.po | 75 + ...lasma_applet_org.kde.plasma.colorpicker.po | 148 + .../plasma_applet_org.kde.plasma.comic.po | 243 ++ .../plasma_applet_org.kde.plasma.diskquota.po | 81 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 107 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 881 +++++ ...applet_org.kde.plasma.keyboardindicator.po | 59 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 30 + ...plasma_applet_org.kde.plasma.mediaframe.po | 200 ++ .../plasma_applet_org.kde.plasma.notes.po | 230 ++ ..._applet_org.kde.plasma.private.grouping.po | 24 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 123 + .../plasma_applet_org.kde.plasma.timer.po | 204 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 131 + .../plasma_applet_org.kde.plasma.weather.po | 554 +++ ...plasma_applet_org.kde.plasma.webbrowser.po | 191 + ...lasma_applet_org.kde.plasma_applet_dict.po | 68 + po/zh_CN/plasma_calendar_alternatecalendar.po | 141 + .../plasma_calendar_astronomicalevents.po | 36 + po/zh_CN/plasma_runner_CharacterRunner.po | 81 + po/zh_CN/plasma_runner_converterrunner.po | 39 + po/zh_CN/plasma_runner_datetime.po | 160 + po/zh_CN/plasma_runner_katesessions.po | 34 + po/zh_CN/plasma_runner_konsoleprofiles.po | 29 + po/zh_CN/plasma_runner_krunner_dictionary.po | 48 + po/zh_CN/plasma_runner_spellcheckrunner.po | 80 + po/zh_CN/plasma_wallpaper_org.kde.potd.po | 209 ++ po/zh_TW/kwin_effect_cube.po | 45 + po/zh_TW/plasma_addons_engine_dict.po | 23 + po/zh_TW/plasma_addons_profiles_utility.po | 28 + ...plet_org.kde.plasma.addons.katesessions.po | 57 + ...lasma_applet_org.kde.plasma.binaryclock.po | 64 + ...plasma_applet_org.kde.plasma.calculator.po | 77 + ...lasma_applet_org.kde.plasma.colorpicker.po | 156 + .../plasma_applet_org.kde.plasma.comic.po | 349 ++ .../plasma_applet_org.kde.plasma.diskquota.po | 81 + ...sma_applet_org.kde.plasma.fifteenpuzzle.po | 112 + ...plasma_applet_org.kde.plasma.fuzzyclock.po | 884 +++++ ...applet_org.kde.plasma.keyboardindicator.po | 68 + ...a_applet_org.kde.plasma.konsoleprofiles.po | 30 + ...plasma_applet_org.kde.plasma.mediaframe.po | 206 ++ .../plasma_applet_org.kde.plasma.notes.po | 239 ++ ..._applet_org.kde.plasma.private.grouping.po | 23 + ...lasma_applet_org.kde.plasma.quicklaunch.po | 124 + .../plasma_applet_org.kde.plasma.timer.po | 210 ++ ...asma_applet_org.kde.plasma.userswitcher.po | 143 + .../plasma_applet_org.kde.plasma.weather.po | 673 ++++ ...plasma_applet_org.kde.plasma.webbrowser.po | 190 + ...lasma_applet_org.kde.plasma_applet_dict.po | 73 + po/zh_TW/plasma_calendar_alternatecalendar.po | 143 + .../plasma_calendar_astronomicalevents.po | 37 + po/zh_TW/plasma_runner_CharacterRunner.po | 82 + po/zh_TW/plasma_runner_converterrunner.po | 40 + po/zh_TW/plasma_runner_datetime.po | 168 + po/zh_TW/plasma_runner_katesessions.po | 34 + po/zh_TW/plasma_runner_konsoleprofiles.po | 28 + po/zh_TW/plasma_runner_krunner_dictionary.po | 49 + po/zh_TW/plasma_runner_spellcheckrunner.po | 93 + po/zh_TW/plasma_wallpaper_org.kde.potd.po | 321 ++ profiles/CMakeLists.txt | 15 + profiles/Messages.sh | 2 + profiles/profilesmodel.cpp | 125 + profiles/profilesmodel.h | 66 + profiles/profilesplugin.cpp | 26 + runners/CMakeLists.txt | 8 + runners/characters/CMakeLists.txt | 19 + runners/characters/Messages.sh | 3 + runners/characters/charrunner.cpp | 83 + runners/characters/charrunner.h | 33 + runners/characters/charrunner_config.cpp | 123 + runners/characters/charrunner_config.h | 44 + runners/characters/charrunner_config.ui | 161 + runners/characters/config_keys.h | 14 + .../characters/plasma-runner-character.json | 143 + runners/converter/CMakeLists.txt | 13 + runners/converter/Messages.sh | 2 + runners/converter/autotests/CMakeLists.txt | 6 + .../autotests/converterrunnertest.cpp | 146 + runners/converter/converterrunner.cpp | 266 ++ runners/converter/converterrunner.h | 56 + .../converter/plasma-runner-converter.json | 143 + runners/datetime/CMakeLists.txt | 12 + runners/datetime/Messages.sh | 2 + runners/datetime/autotests/CMakeLists.txt | 6 + .../datetime/autotests/datetimerunnertest.cpp | 182 + runners/datetime/datetimerunner.cpp | 351 ++ runners/datetime/datetimerunner.h | 35 + runners/datetime/plasma-runner-datetime.json | 142 + runners/dictionary/CMakeLists.txt | 10 + runners/dictionary/Messages.sh | 2 + runners/dictionary/dictionaryrunner.cpp | 123 + runners/dictionary/dictionaryrunner.h | 29 + .../dictionary/dictionaryrunner_config.cpp | 56 + runners/dictionary/dictionaryrunner_config.h | 29 + .../dictionary/plasma-runner-dictionary.json | 145 + runners/katesessions/CMakeLists.txt | 4 + runners/katesessions/Messages.sh | 2 + runners/katesessions/README | 25 + runners/katesessions/katesessions.cpp | 103 + runners/katesessions/katesessions.h | 33 + .../plasma-runner-katesessions.json | 141 + runners/konsoleprofiles/CMakeLists.txt | 9 + runners/konsoleprofiles/Messages.sh | 2 + runners/konsoleprofiles/konsoleprofiles.cpp | 63 + runners/konsoleprofiles/konsoleprofiles.h | 32 + .../plasma-runner-konsoleprofiles.json | 141 + runners/spellchecker/CMakeLists.txt | 30 + runners/spellchecker/Messages.sh | 3 + runners/spellchecker/autotests/CMakeLists.txt | 12 + .../autotests/spellcheckrunnertest.cpp | 156 + .../plasma-runner-spellchecker.json | 142 + runners/spellchecker/spellcheck.cpp | 270 ++ runners/spellchecker/spellcheck.h | 48 + runners/spellchecker/spellcheck_config.cpp | 110 + runners/spellchecker/spellcheck_config.h | 40 + runners/spellchecker/spellcheck_config.ui | 88 + templates/.clang-format | 2 + templates/CMakeLists.txt | 5 + templates/plasmapotdprovider/CMakeLists.txt | 34 + templates/plasmapotdprovider/README | 29 + .../plasmapotdprovider.kdevtemplate | 6 + .../plasmapotdprovider/src/%{APPNAMELC}.cpp | 66 + .../plasmapotdprovider/src/%{APPNAMELC}.h | 39 + .../plasmapotdprovider/src/%{APPNAMELC}.json | 13 + .../plasmapotdprovider/src/CMakeLists.txt | 11 + wallpapers/CMakeLists.txt | 3 + .../haenau/contents/ui/BackgroundElement.qml | 43 + .../contents/ui/BottomBackgroundElement.qml | 46 + .../contents/ui/RightBackgroundElement.qml | 46 + wallpapers/haenau/contents/ui/main.qml | 105 + wallpapers/haenau/contents/ui/wallpaper.svgz | Bin 0 -> 6268 bytes wallpapers/haenau/metadata.json | 143 + .../hunyango/contents/ui/ColorProvider.qml | 48 + wallpapers/hunyango/contents/ui/main.qml | 46 + wallpapers/hunyango/contents/ui/qmldir | 4 + .../hunyango/contents/ui/wallpaper.svgz | Bin 0 -> 3010 bytes wallpapers/hunyango/metadata.json | 143 + wallpapers/potd/CMakeLists.txt | 2 + wallpapers/potd/Messages.sh | 4 + .../potd/package/contents/config/main.xml | 27 + .../package/contents/ui/ActionContextMenu.qml | 37 + .../package/contents/ui/WallpaperDelegate.qml | 260 ++ .../package/contents/ui/WallpaperPreview.qml | 64 + .../potd/package/contents/ui/config.qml | 218 ++ wallpapers/potd/package/contents/ui/main.qml | 131 + wallpapers/potd/package/metadata.json | 144 + wallpapers/potd/plugins/CMakeLists.txt | 90 + wallpapers/potd/plugins/HowToAddProvider.txt | 20 + .../potd/plugins/PlasmaPotdProvider.cmake.in | 7 + wallpapers/potd/plugins/cachedprovider.cpp | 151 + wallpapers/potd/plugins/cachedprovider.h | 96 + .../potd/plugins/plasma_potdprovider.json | 47 + wallpapers/potd/plugins/potdbackend.cpp | 284 ++ wallpapers/potd/plugins/potdbackend.h | 143 + wallpapers/potd/plugins/potdengine.cpp | 401 +++ wallpapers/potd/plugins/potdengine.h | 119 + wallpapers/potd/plugins/potdplugin.cpp | 30 + wallpapers/potd/plugins/potdprovider.cpp | 93 + wallpapers/potd/plugins/potdprovider.h | 118 + wallpapers/potd/plugins/potdprovidermodel.cpp | 90 + wallpapers/potd/plugins/potdprovidermodel.h | 40 + .../potd/plugins/providers/CMakeLists.txt | 22 + .../potd/plugins/providers/PoTD-list.txt | 55 + .../potd/plugins/providers/apodprovider.cpp | 81 + .../potd/plugins/providers/apodprovider.h | 33 + .../potd/plugins/providers/apodprovider.json | 51 + .../potd/plugins/providers/bingprovider.cpp | 107 + .../potd/plugins/providers/bingprovider.h | 30 + .../potd/plugins/providers/bingprovider.json | 51 + .../potd/plugins/providers/epodprovider.cpp | 91 + .../potd/plugins/providers/epodprovider.h | 29 + .../potd/plugins/providers/epodprovider.json | 50 + .../potd/plugins/providers/flickrprovider.cpp | 269 ++ .../potd/plugins/providers/flickrprovider.h | 71 + .../plugins/providers/flickrprovider.json | 51 + .../potd/plugins/providers/noaaprovider.cpp | 114 + .../potd/plugins/providers/noaaprovider.h | 31 + .../potd/plugins/providers/noaaprovider.json | 50 + .../plugins/providers/pixabayprovider.conf | 2 + .../providers/simonstalenhagprovider.cpp | 111 + .../providers/simonstalenhagprovider.h | 32 + .../providers/simonstalenhagprovider.json | 49 + .../potd/plugins/providers/wcpotdprovider.cpp | 95 + .../potd/plugins/providers/wcpotdprovider.h | 32 + .../plugins/providers/wcpotdprovider.json | 51 + 2291 files changed, 367070 insertions(+) create mode 100644 .git-blame-ignore-revs create mode 100644 .gitignore create mode 100644 .gitlab-ci.yml create mode 100644 .kde-ci.yml create mode 100644 CMakeLists.txt create mode 100644 LICENSES/BSD-3-Clause.txt create mode 100644 LICENSES/CC0-1.0.txt create mode 100644 LICENSES/GPL-2.0-only.txt create mode 100644 LICENSES/GPL-2.0-or-later.txt create mode 100644 LICENSES/GPL-3.0-only.txt create mode 100644 LICENSES/GPL-3.0-or-later.txt create mode 100644 LICENSES/LGPL-2.0-only.txt create mode 100644 LICENSES/LGPL-2.0-or-later.txt create mode 100644 LICENSES/LGPL-2.1-only.txt create mode 100644 LICENSES/LGPL-2.1-or-later.txt create mode 100644 LICENSES/LGPL-3.0-only.txt create mode 100644 LICENSES/LicenseRef-KDE-Accepted-GPL.txt create mode 100644 LICENSES/LicenseRef-KDE-Accepted-LGPL.txt create mode 100644 LICENSES/MIT.txt create mode 100644 Mainpage.dox create mode 100644 README.md create mode 100644 appiumtests/CMakeLists.txt create mode 100755 appiumtests/calculatortest.py create mode 100644 appiumtests/calendarplugins/CMakeLists.txt create mode 100755 appiumtests/calendarplugins/alternatecalendartest.py create mode 100755 appiumtests/keyboardindicatortest.py create mode 100644 applets/CMakeLists.txt create mode 100755 applets/binary-clock/Messages.sh create mode 100644 applets/binary-clock/package/contents/config/config.qml create mode 100644 applets/binary-clock/package/contents/config/main.xml create mode 100644 applets/binary-clock/package/contents/ui/BinaryClock.qml create mode 100644 applets/binary-clock/package/contents/ui/configGeneral.qml create mode 100644 applets/binary-clock/package/contents/ui/main.qml create mode 100644 applets/binary-clock/package/metadata.json create mode 100755 applets/calculator/Messages.sh create mode 100644 applets/calculator/package/contents/ui/main.qml create mode 100644 applets/calculator/package/metadata.json create mode 100644 applets/colorpicker/CMakeLists.txt create mode 100755 applets/colorpicker/Messages.sh create mode 100644 applets/colorpicker/package/contents/config/config.qml create mode 100644 applets/colorpicker/package/contents/config/main.xml create mode 100644 applets/colorpicker/package/contents/ui/ColorCircle.qml create mode 100644 applets/colorpicker/package/contents/ui/ColorContextMenu.qml create mode 100644 applets/colorpicker/package/contents/ui/CompactRepresentation.qml create mode 100644 applets/colorpicker/package/contents/ui/ImageColors.qml create mode 100644 applets/colorpicker/package/contents/ui/LoadingIndicator.qml create mode 100644 applets/colorpicker/package/contents/ui/configGeneral.qml create mode 100644 applets/colorpicker/package/contents/ui/logic.js create mode 100644 applets/colorpicker/package/contents/ui/main.qml create mode 100644 applets/colorpicker/package/metadata.json create mode 100644 applets/colorpicker/plugin/colorpickerplugin.cpp create mode 100644 applets/colorpicker/plugin/grabwidget.cpp create mode 100644 applets/colorpicker/plugin/grabwidget.h create mode 100644 applets/comic/CMakeLists.txt create mode 100755 applets/comic/Messages.sh create mode 100644 applets/comic/activecomicmodel.cpp create mode 100644 applets/comic/activecomicmodel.h create mode 100644 applets/comic/checknewstrips.cpp create mode 100644 applets/comic/checknewstrips.h create mode 100644 applets/comic/comic.cpp create mode 100644 applets/comic/comic.h create mode 100644 applets/comic/comic.knsrc create mode 100644 applets/comic/comicdata.cpp create mode 100644 applets/comic/comicdata.h create mode 100644 applets/comic/comicinfo.cpp create mode 100644 applets/comic/comicinfo.h create mode 100644 applets/comic/comicmodel.cpp create mode 100644 applets/comic/comicmodel.h create mode 100644 applets/comic/comicsaver.cpp create mode 100644 applets/comic/comicsaver.h create mode 100644 applets/comic/engine/CMakeLists.txt create mode 100644 applets/comic/engine/cachedprovider.cpp create mode 100644 applets/comic/engine/cachedprovider.h create mode 100644 applets/comic/engine/comic.cpp create mode 100644 applets/comic/engine/comic.h create mode 100644 applets/comic/engine/comic_package.cpp create mode 100644 applets/comic/engine/comicprovider.cpp create mode 100644 applets/comic/engine/comicprovider.h create mode 100644 applets/comic/engine/comicproviderkross.cpp create mode 100644 applets/comic/engine/comicproviderkross.h create mode 100644 applets/comic/engine/comicproviderwrapper.cpp create mode 100644 applets/comic/engine/comicproviderwrapper.h create mode 100644 applets/comic/engine/plasma-packagestructure-comic.json create mode 100644 applets/comic/engine/types.h create mode 100644 applets/comic/package/contents/config/config.qml create mode 100644 applets/comic/package/contents/ui/ButtonBar.qml create mode 100644 applets/comic/package/contents/ui/ComicBottomInfo.qml create mode 100644 applets/comic/package/contents/ui/ComicCentralView.qml create mode 100644 applets/comic/package/contents/ui/FullViewWidget.qml create mode 100644 applets/comic/package/contents/ui/ImageWidget.qml create mode 100644 applets/comic/package/contents/ui/configAdvanced.qml create mode 100644 applets/comic/package/contents/ui/configAppearance.qml create mode 100644 applets/comic/package/contents/ui/configGeneral.qml create mode 100644 applets/comic/package/contents/ui/main.qml create mode 100644 applets/comic/package/metadata.json create mode 100644 applets/comic/stripselector.cpp create mode 100644 applets/comic/stripselector.h create mode 100644 applets/comic/stripselector_p.h create mode 100644 applets/dict/CMakeLists.txt create mode 100755 applets/dict/Messages.sh create mode 100644 applets/dict/package/contents/config/config.qml create mode 100644 applets/dict/package/contents/config/main.xml create mode 100644 applets/dict/package/contents/ui/AvailableDictSheet.qml create mode 100644 applets/dict/package/contents/ui/ConfigDictionaries.qml create mode 100644 applets/dict/package/contents/ui/DictItemDelegate.qml create mode 100644 applets/dict/package/contents/ui/main.qml create mode 100644 applets/dict/package/metadata.json create mode 100644 applets/dict/plugin/dict_object.cpp create mode 100644 applets/dict/plugin/dict_object.h create mode 100644 applets/dict/plugin/dict_plugin.cpp create mode 100644 applets/dict/plugin/dictionariesmodel.cpp create mode 100644 applets/dict/plugin/dictionariesmodel.h create mode 100644 applets/dict/sc-apps-accessories-dictionary.svgz create mode 100644 applets/diskquota/CMakeLists.txt create mode 100644 applets/diskquota/Messages.sh create mode 100644 applets/diskquota/package/contents/ui/ListDelegateItem.qml create mode 100644 applets/diskquota/package/contents/ui/main.qml create mode 100644 applets/diskquota/package/metadata.json create mode 100644 applets/diskquota/plugin/DiskQuota.cpp create mode 100644 applets/diskquota/plugin/DiskQuota.h create mode 100644 applets/diskquota/plugin/QuotaItem.cpp create mode 100644 applets/diskquota/plugin/QuotaItem.h create mode 100644 applets/diskquota/plugin/QuotaListModel.cpp create mode 100644 applets/diskquota/plugin/QuotaListModel.h create mode 100644 applets/diskquota/plugin/plugin.cpp create mode 100644 applets/fifteenPuzzle/CMakeLists.txt create mode 100755 applets/fifteenPuzzle/Messages.sh create mode 100644 applets/fifteenPuzzle/package/contents/config/config.qml create mode 100644 applets/fifteenPuzzle/package/contents/config/main.xml create mode 100644 applets/fifteenPuzzle/package/contents/ui/FifteenPuzzle.qml create mode 100644 applets/fifteenPuzzle/package/contents/ui/Piece.qml create mode 100644 applets/fifteenPuzzle/package/contents/ui/blanksquare.svg create mode 100644 applets/fifteenPuzzle/package/contents/ui/configAppearance.qml create mode 100644 applets/fifteenPuzzle/package/contents/ui/main.qml create mode 100644 applets/fifteenPuzzle/package/metadata.json create mode 100644 applets/fifteenPuzzle/plugin/fifteenimageprovider.cpp create mode 100644 applets/fifteenPuzzle/plugin/fifteenimageprovider.h create mode 100644 applets/fifteenPuzzle/plugin/fifteenpuzzleplugin.cpp create mode 100644 applets/fifteenPuzzle/sc-apps-fifteenpuzzle.svgz create mode 100755 applets/fuzzy-clock/Messages.sh create mode 100644 applets/fuzzy-clock/package/contents/config/config.qml create mode 100644 applets/fuzzy-clock/package/contents/config/main.xml create mode 100644 applets/fuzzy-clock/package/contents/ui/FuzzyClock.qml create mode 100644 applets/fuzzy-clock/package/contents/ui/configAppearance.qml create mode 100644 applets/fuzzy-clock/package/contents/ui/main.qml create mode 100644 applets/fuzzy-clock/package/metadata.json create mode 100644 applets/grouping/CMakeLists.txt create mode 100755 applets/grouping/Messages.sh create mode 100644 applets/grouping/container/CMakeLists.txt create mode 100644 applets/grouping/container/groupedappletscontainer.cpp create mode 100644 applets/grouping/container/groupedappletscontainer.h create mode 100644 applets/grouping/container/package/contents/ui/main.qml create mode 100644 applets/grouping/container/package/metadata.json create mode 100644 applets/grouping/groupingcontainment.cpp create mode 100644 applets/grouping/groupingcontainment.h create mode 100644 applets/grouping/package/contents/applet/CompactApplet.qml create mode 100644 applets/grouping/package/contents/config/config.qml create mode 100644 applets/grouping/package/contents/config/main.xml create mode 100644 applets/grouping/package/contents/ui/items/AbstractItem.qml create mode 100644 applets/grouping/package/contents/ui/items/PlasmoidItem.qml create mode 100644 applets/grouping/package/contents/ui/main.qml create mode 100644 applets/grouping/package/metadata.json create mode 100644 applets/katesessions/Messages.sh create mode 100644 applets/katesessions/contents/ui/KateSessionsItemDelegate.qml create mode 100644 applets/katesessions/contents/ui/Menu.qml create mode 100644 applets/katesessions/contents/ui/main.qml create mode 100644 applets/katesessions/metadata.json create mode 100644 applets/keyboardindicator/Messages.sh create mode 100644 applets/keyboardindicator/contents/config/config.qml create mode 100644 applets/keyboardindicator/contents/config/main.xml create mode 100644 applets/keyboardindicator/contents/ui/configAppearance.qml create mode 100644 applets/keyboardindicator/contents/ui/main.qml create mode 100644 applets/keyboardindicator/metadata.json create mode 100644 applets/kickerdash/metadata.json create mode 100644 applets/konsoleprofiles/Messages.sh create mode 100644 applets/konsoleprofiles/package/contents/ui/main.qml create mode 100644 applets/konsoleprofiles/package/metadata.json create mode 100644 applets/mediaframe/CMakeLists.txt create mode 100644 applets/mediaframe/Messages.sh create mode 100644 applets/mediaframe/package/contents/config/config.qml create mode 100644 applets/mediaframe/package/contents/config/main.xml create mode 100644 applets/mediaframe/package/contents/ui/ConfigGeneral.qml create mode 100644 applets/mediaframe/package/contents/ui/ConfigPaths.qml create mode 100644 applets/mediaframe/package/contents/ui/main.qml create mode 100644 applets/mediaframe/package/metadata.json create mode 100644 applets/mediaframe/plugin/mediaframe.cpp create mode 100644 applets/mediaframe/plugin/mediaframe.h create mode 100644 applets/mediaframe/plugin/mediaframeplugin.cpp create mode 100644 applets/notes/CMakeLists.txt create mode 100755 applets/notes/Messages.sh create mode 100644 applets/notes/package/contents/config/config.qml create mode 100644 applets/notes/package/contents/config/main.xml create mode 100644 applets/notes/package/contents/ui/ShortcutMenuItem.qml create mode 100644 applets/notes/package/contents/ui/configAppearance.qml create mode 100644 applets/notes/package/contents/ui/main.qml create mode 100644 applets/notes/package/metadata.json create mode 100644 applets/notes/plugin/abstractnoteloader.cpp create mode 100644 applets/notes/plugin/abstractnoteloader.h create mode 100644 applets/notes/plugin/documenthandler.cpp create mode 100644 applets/notes/plugin/documenthandler.h create mode 100644 applets/notes/plugin/filesystemnoteloader.cpp create mode 100644 applets/notes/plugin/filesystemnoteloader.h create mode 100644 applets/notes/plugin/note.cpp create mode 100644 applets/notes/plugin/note.h create mode 100644 applets/notes/plugin/notemanager.cpp create mode 100644 applets/notes/plugin/notemanager.h create mode 100644 applets/notes/plugin/notesplugin.cpp create mode 100644 applets/quicklaunch/CMakeLists.txt create mode 100644 applets/quicklaunch/Messages.sh create mode 100644 applets/quicklaunch/package/contents/config/config.qml create mode 100644 applets/quicklaunch/package/contents/config/main.xml create mode 100644 applets/quicklaunch/package/contents/ui/ConfigGeneral.qml create mode 100644 applets/quicklaunch/package/contents/ui/IconItem.qml create mode 100644 applets/quicklaunch/package/contents/ui/Popup.qml create mode 100644 applets/quicklaunch/package/contents/ui/UrlModel.qml create mode 100644 applets/quicklaunch/package/contents/ui/layout.js create mode 100644 applets/quicklaunch/package/contents/ui/main.qml create mode 100644 applets/quicklaunch/package/metadata.json create mode 100644 applets/quicklaunch/plugin/CMakeLists.txt create mode 100644 applets/quicklaunch/plugin/quicklaunch_p.cpp create mode 100644 applets/quicklaunch/plugin/quicklaunch_p.h create mode 100644 applets/quicklaunch/plugin/quicklaunchplugin.cpp create mode 100644 applets/timer/CMakeLists.txt create mode 100755 applets/timer/Messages.sh create mode 100644 applets/timer/package/contents/config/config.qml create mode 100644 applets/timer/package/contents/config/main.xml create mode 100644 applets/timer/package/contents/ui/CompactRepresentation.qml create mode 100644 applets/timer/package/contents/ui/TimerEdit.qml create mode 100644 applets/timer/package/contents/ui/TimerView.qml create mode 100644 applets/timer/package/contents/ui/configAdvanced.qml create mode 100644 applets/timer/package/contents/ui/configAppearance.qml create mode 100644 applets/timer/package/contents/ui/configTimes.qml create mode 100644 applets/timer/package/contents/ui/main.qml create mode 100644 applets/timer/package/metadata.json create mode 100644 applets/timer/plasma_applet_timer.notifyrc create mode 100644 applets/timer/plugin/timerplugin.cpp create mode 100644 applets/timer/timer.svgz create mode 100755 applets/userswitcher/Messages.sh create mode 100644 applets/userswitcher/package/contents/config/config.qml create mode 100644 applets/userswitcher/package/contents/config/main.xml create mode 100644 applets/userswitcher/package/contents/ui/ActionListDelegate.qml create mode 100644 applets/userswitcher/package/contents/ui/ListDelegate.qml create mode 100644 applets/userswitcher/package/contents/ui/UserListDelegate.qml create mode 100644 applets/userswitcher/package/contents/ui/configGeneral.qml create mode 100644 applets/userswitcher/package/contents/ui/main.qml create mode 100644 applets/userswitcher/package/metadata.json create mode 100644 applets/weather/CMakeLists.txt create mode 100755 applets/weather/Messages.sh create mode 100644 applets/weather/data/i18n.dat create mode 100644 applets/weather/package/contents/config/config.qml create mode 100644 applets/weather/package/contents/config/main.xml create mode 100644 applets/weather/package/contents/ui/CompactRepresentation.qml create mode 100644 applets/weather/package/contents/ui/DetailsView.qml create mode 100644 applets/weather/package/contents/ui/ForecastView.qml create mode 100644 applets/weather/package/contents/ui/FullRepresentation.qml create mode 100644 applets/weather/package/contents/ui/IconAndTextItem.qml create mode 100644 applets/weather/package/contents/ui/NoticesView.qml create mode 100644 applets/weather/package/contents/ui/SwitchPanel.qml create mode 100644 applets/weather/package/contents/ui/TopPanel.qml create mode 100644 applets/weather/package/contents/ui/config/ConfigAppearance.qml create mode 100644 applets/weather/package/contents/ui/config/ConfigUnits.qml create mode 100644 applets/weather/package/contents/ui/config/ConfigWeatherStation.qml create mode 100644 applets/weather/package/contents/ui/main.qml create mode 100644 applets/weather/package/metadata.json create mode 100644 applets/weather/plugin/abstractunitlistmodel.cpp create mode 100644 applets/weather/plugin/abstractunitlistmodel.h create mode 100644 applets/weather/plugin/locationlistmodel.cpp create mode 100644 applets/weather/plugin/locationlistmodel.h create mode 100644 applets/weather/plugin/plugin.cpp create mode 100644 applets/weather/plugin/util.cpp create mode 100644 applets/weather/plugin/util.h create mode 100644 applets/weather/weatherapplet.cpp create mode 100644 applets/weather/weatherapplet.h create mode 100644 applets/weather/wind-arrows.svgz create mode 100644 applets/webbrowser/CMakeLists.txt create mode 100644 applets/webbrowser/Messages.sh create mode 100644 applets/webbrowser/package/contents/config/config.qml create mode 100644 applets/webbrowser/package/contents/config/main.xml create mode 100644 applets/webbrowser/package/contents/ui/ConfigAppearance.qml create mode 100644 applets/webbrowser/package/contents/ui/ConfigGeneral.qml create mode 100644 applets/webbrowser/package/contents/ui/main.qml create mode 100644 applets/webbrowser/package/metadata.json create mode 100644 dict/CMakeLists.txt create mode 100644 dict/Messages.sh create mode 100644 dict/dictengine.cpp create mode 100644 dict/dictengine.h create mode 100644 kdeds/CMakeLists.txt create mode 100644 kdeds/kameleon/CMakeLists.txt create mode 100644 kdeds/kameleon/kameleon.cpp create mode 100644 kdeds/kameleon/kameleon.h create mode 100644 kdeds/kameleon/kameleon.json create mode 100644 kdeds/kameleon/kameleon.json.license create mode 100644 kdeds/kameleon/kameleonhelper.actions create mode 100644 kdeds/kameleon/kameleonhelper.cpp create mode 100644 kdeds/kameleon/kameleonhelper.h create mode 100644 kwin/CMakeLists.txt create mode 100644 kwin/effects/CMakeLists.txt create mode 100644 kwin/effects/cube/CMakeLists.txt create mode 100644 kwin/effects/cube/Messages.sh create mode 100644 kwin/effects/cube/kcm/CMakeLists.txt create mode 100644 kwin/effects/cube/kcm/cubeeffectkcm.cpp create mode 100644 kwin/effects/cube/kcm/cubeeffectkcm.h create mode 100644 kwin/effects/cube/kcm/cubeeffectkcm.ui create mode 100644 kwin/effects/cube/kcm/cubeeffectkcm.ui.license create mode 100644 kwin/effects/cube/kcm/resources.qrc create mode 100644 kwin/effects/cube/kcm/resources.qrc.license create mode 100644 kwin/effects/cube/package/contents/config/main.xml create mode 100644 kwin/effects/cube/package/contents/ui/Cube.qml create mode 100644 kwin/effects/cube/package/contents/ui/CubeCameraController.qml create mode 100644 kwin/effects/cube/package/contents/ui/CubeFace.qml create mode 100644 kwin/effects/cube/package/contents/ui/DesktopView.qml create mode 100644 kwin/effects/cube/package/contents/ui/PlaceholderView.qml create mode 100644 kwin/effects/cube/package/contents/ui/ScreenView.qml create mode 100644 kwin/effects/cube/package/contents/ui/constants.js create mode 100644 kwin/effects/cube/package/contents/ui/main.qml create mode 100644 kwin/effects/cube/package/metadata.json create mode 100644 kwin/windowswitchers/CMakeLists.txt create mode 100644 kwin/windowswitchers/big_icons/contents/ui/main.qml create mode 100644 kwin/windowswitchers/big_icons/metadata.json create mode 100644 kwin/windowswitchers/compact/contents/ui/main.qml create mode 100644 kwin/windowswitchers/compact/metadata.json create mode 100644 kwin/windowswitchers/coverswitch/contents/ui/main.qml create mode 100644 kwin/windowswitchers/coverswitch/metadata.json create mode 100644 kwin/windowswitchers/flipswitch/contents/ui/main.qml create mode 100644 kwin/windowswitchers/flipswitch/metadata.json create mode 100644 kwin/windowswitchers/sidebar/contents/ui/main.qml create mode 100644 kwin/windowswitchers/sidebar/metadata.json create mode 100644 plasmacalendarplugins/CMakeLists.txt create mode 100644 plasmacalendarplugins/alternatecalendar/CMakeLists.txt create mode 100644 plasmacalendarplugins/alternatecalendar/Messages.sh create mode 100644 plasmacalendarplugins/alternatecalendar/alternatecalendarplugin.cpp create mode 100644 plasmacalendarplugins/alternatecalendar/alternatecalendarplugin.h create mode 100644 plasmacalendarplugins/alternatecalendar/alternatecalendarplugin.json create mode 100644 plasmacalendarplugins/alternatecalendar/calendarsystem.h create mode 100644 plasmacalendarplugins/alternatecalendar/config-ICU.h.cmake create mode 100644 plasmacalendarplugins/alternatecalendar/config/CMakeLists.txt create mode 100644 plasmacalendarplugins/alternatecalendar/config/plugin/CMakeLists.txt create mode 100644 plasmacalendarplugins/alternatecalendar/config/plugin/configstorage.cpp create mode 100644 plasmacalendarplugins/alternatecalendar/config/plugin/configstorage.h create mode 100644 plasmacalendarplugins/alternatecalendar/config/qml/AlternateCalendarConfig.qml create mode 100644 plasmacalendarplugins/alternatecalendar/config/qml/CMakeLists.txt create mode 100644 plasmacalendarplugins/alternatecalendar/provider/abstractcalendarprovider.cpp create mode 100644 plasmacalendarplugins/alternatecalendar/provider/abstractcalendarprovider.h create mode 100644 plasmacalendarplugins/alternatecalendar/provider/chinesecalendar.cpp create mode 100644 plasmacalendarplugins/alternatecalendar/provider/chinesecalendar.h create mode 100644 plasmacalendarplugins/alternatecalendar/provider/hebrewcalendar.cpp create mode 100644 plasmacalendarplugins/alternatecalendar/provider/hebrewcalendar.h create mode 100644 plasmacalendarplugins/alternatecalendar/provider/icucalendar_p.cpp create mode 100644 plasmacalendarplugins/alternatecalendar/provider/icucalendar_p.h create mode 100644 plasmacalendarplugins/alternatecalendar/provider/indiancalendar.cpp create mode 100644 plasmacalendarplugins/alternatecalendar/provider/indiancalendar.h create mode 100644 plasmacalendarplugins/alternatecalendar/provider/islamiccalendar.cpp create mode 100644 plasmacalendarplugins/alternatecalendar/provider/islamiccalendar.h create mode 100644 plasmacalendarplugins/alternatecalendar/provider/qtcalendar.cpp create mode 100644 plasmacalendarplugins/alternatecalendar/provider/qtcalendar.h create mode 100644 plasmacalendarplugins/alternatecalendar/provider/solarutils.h create mode 100644 plasmacalendarplugins/astronomical/CMakeLists.txt create mode 100644 plasmacalendarplugins/astronomical/Messages.sh create mode 100644 plasmacalendarplugins/astronomical/astronomicaleventsplugin.cpp create mode 100644 plasmacalendarplugins/astronomical/astronomicaleventsplugin.h create mode 100644 plasmacalendarplugins/astronomical/astronomicaleventsplugin.json create mode 100644 plasmacalendarplugins/astronomical/config/CMakeLists.txt create mode 100644 plasmacalendarplugins/astronomical/config/plugin/CMakeLists.txt create mode 100644 plasmacalendarplugins/astronomical/config/plugin/configplugin.cpp create mode 100644 plasmacalendarplugins/astronomical/config/plugin/configstorage.cpp create mode 100644 plasmacalendarplugins/astronomical/config/plugin/configstorage.h create mode 100644 plasmacalendarplugins/astronomical/config/qml/AstronomicalEventsConfig.qml create mode 100644 plasmacalendarplugins/astronomical/config/qml/CMakeLists.txt create mode 100644 po/ar/kwin_effect_cube.po create mode 100644 po/ar/plasma_addons_engine_dict.po create mode 100644 po/ar/plasma_addons_profiles_utility.po create mode 100644 po/ar/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/ar/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/ar/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/ar/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/ar/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/ar/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/ar/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/ar/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/ar/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/ar/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/ar/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/ar/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/ar/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/ar/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/ar/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/ar/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/ar/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/ar/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/ar/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/ar/plasma_calendar_alternatecalendar.po create mode 100644 po/ar/plasma_calendar_astronomicalevents.po create mode 100644 po/ar/plasma_runner_CharacterRunner.po create mode 100644 po/ar/plasma_runner_converterrunner.po create mode 100644 po/ar/plasma_runner_datetime.po create mode 100644 po/ar/plasma_runner_katesessions.po create mode 100644 po/ar/plasma_runner_konsoleprofiles.po create mode 100644 po/ar/plasma_runner_krunner_dictionary.po create mode 100644 po/ar/plasma_runner_spellcheckrunner.po create mode 100644 po/ar/plasma_wallpaper_org.kde.potd.po create mode 100644 po/ast/kwin_effect_cube.po create mode 100644 po/ast/plasma_addons_engine_dict.po create mode 100644 po/ast/plasma_addons_profiles_utility.po create mode 100644 po/ast/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/ast/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/ast/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/ast/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/ast/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/ast/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/ast/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/ast/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/ast/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/ast/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/ast/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/ast/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/ast/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/ast/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/ast/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/ast/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/ast/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/ast/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/ast/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/ast/plasma_calendar_alternatecalendar.po create mode 100644 po/ast/plasma_calendar_astronomicalevents.po create mode 100644 po/ast/plasma_runner_CharacterRunner.po create mode 100644 po/ast/plasma_runner_converterrunner.po create mode 100644 po/ast/plasma_runner_datetime.po create mode 100644 po/ast/plasma_runner_katesessions.po create mode 100644 po/ast/plasma_runner_konsoleprofiles.po create mode 100644 po/ast/plasma_runner_krunner_dictionary.po create mode 100644 po/ast/plasma_runner_spellcheckrunner.po create mode 100644 po/ast/plasma_wallpaper_org.kde.potd.po create mode 100644 po/az/plasma_addons_engine_dict.po create mode 100644 po/az/plasma_addons_profiles_utility.po create mode 100644 po/az/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/az/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/az/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/az/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/az/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/az/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/az/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/az/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/az/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/az/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/az/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/az/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/az/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/az/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/az/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/az/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/az/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/az/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/az/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/az/plasma_calendar_alternatecalendar.po create mode 100644 po/az/plasma_calendar_astronomicalevents.po create mode 100644 po/az/plasma_runner_CharacterRunner.po create mode 100644 po/az/plasma_runner_converterrunner.po create mode 100644 po/az/plasma_runner_datetime.po create mode 100644 po/az/plasma_runner_katesessions.po create mode 100644 po/az/plasma_runner_konsoleprofiles.po create mode 100644 po/az/plasma_runner_krunner_dictionary.po create mode 100644 po/az/plasma_runner_spellcheckrunner.po create mode 100644 po/az/plasma_wallpaper_org.kde.potd.po create mode 100644 po/be/plasma_addons_engine_dict.po create mode 100644 po/be/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/be/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/be/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/be/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/be/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/be/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/be/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/be/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/be/plasma_runner_converterrunner.po create mode 100644 po/be/plasma_runner_spellcheckrunner.po create mode 100644 po/bg/kwin_effect_cube.po create mode 100644 po/bg/plasma_addons_engine_dict.po create mode 100644 po/bg/plasma_addons_profiles_utility.po create mode 100644 po/bg/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/bg/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/bg/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/bg/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/bg/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/bg/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/bg/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/bg/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/bg/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/bg/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/bg/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/bg/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/bg/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/bg/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/bg/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/bg/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/bg/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/bg/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/bg/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/bg/plasma_calendar_alternatecalendar.po create mode 100644 po/bg/plasma_calendar_astronomicalevents.po create mode 100644 po/bg/plasma_runner_CharacterRunner.po create mode 100644 po/bg/plasma_runner_converterrunner.po create mode 100644 po/bg/plasma_runner_datetime.po create mode 100644 po/bg/plasma_runner_katesessions.po create mode 100644 po/bg/plasma_runner_konsoleprofiles.po create mode 100644 po/bg/plasma_runner_krunner_dictionary.po create mode 100644 po/bg/plasma_runner_spellcheckrunner.po create mode 100644 po/bg/plasma_wallpaper_org.kde.potd.po create mode 100644 po/bs/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/bs/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/bs/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/bs/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/bs/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/bs/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/bs/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/bs/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/bs/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/bs/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/bs/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/bs/plasma_runner_CharacterRunner.po create mode 100644 po/bs/plasma_runner_converterrunner.po create mode 100644 po/bs/plasma_runner_datetime.po create mode 100644 po/bs/plasma_runner_katesessions.po create mode 100644 po/bs/plasma_runner_krunner_dictionary.po create mode 100644 po/bs/plasma_runner_spellcheckrunner.po create mode 100644 po/ca/kwin_effect_cube.po create mode 100644 po/ca/plasma_addons_engine_dict.po create mode 100644 po/ca/plasma_addons_profiles_utility.po create mode 100644 po/ca/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/ca/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/ca/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/ca/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/ca/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/ca/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/ca/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/ca/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/ca/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/ca/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/ca/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/ca/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/ca/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/ca/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/ca/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/ca/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/ca/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/ca/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/ca/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/ca/plasma_calendar_alternatecalendar.po create mode 100644 po/ca/plasma_calendar_astronomicalevents.po create mode 100644 po/ca/plasma_runner_CharacterRunner.po create mode 100644 po/ca/plasma_runner_converterrunner.po create mode 100644 po/ca/plasma_runner_datetime.po create mode 100644 po/ca/plasma_runner_katesessions.po create mode 100644 po/ca/plasma_runner_konsoleprofiles.po create mode 100644 po/ca/plasma_runner_krunner_dictionary.po create mode 100644 po/ca/plasma_runner_spellcheckrunner.po create mode 100644 po/ca/plasma_wallpaper_org.kde.potd.po create mode 100644 po/ca@valencia/kwin_effect_cube.po create mode 100644 po/ca@valencia/plasma_addons_engine_dict.po create mode 100644 po/ca@valencia/plasma_addons_profiles_utility.po create mode 100644 po/ca@valencia/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/ca@valencia/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/ca@valencia/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/ca@valencia/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/ca@valencia/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/ca@valencia/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/ca@valencia/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/ca@valencia/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/ca@valencia/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/ca@valencia/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/ca@valencia/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/ca@valencia/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/ca@valencia/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/ca@valencia/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/ca@valencia/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/ca@valencia/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/ca@valencia/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/ca@valencia/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/ca@valencia/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/ca@valencia/plasma_calendar_alternatecalendar.po create mode 100644 po/ca@valencia/plasma_calendar_astronomicalevents.po create mode 100644 po/ca@valencia/plasma_runner_CharacterRunner.po create mode 100644 po/ca@valencia/plasma_runner_converterrunner.po create mode 100644 po/ca@valencia/plasma_runner_datetime.po create mode 100644 po/ca@valencia/plasma_runner_katesessions.po create mode 100644 po/ca@valencia/plasma_runner_konsoleprofiles.po create mode 100644 po/ca@valencia/plasma_runner_krunner_dictionary.po create mode 100644 po/ca@valencia/plasma_runner_spellcheckrunner.po create mode 100644 po/ca@valencia/plasma_wallpaper_org.kde.potd.po create mode 100644 po/cs/kwin_effect_cube.po create mode 100644 po/cs/plasma_addons_engine_dict.po create mode 100644 po/cs/plasma_addons_profiles_utility.po create mode 100644 po/cs/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/cs/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/cs/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/cs/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/cs/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/cs/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/cs/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/cs/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/cs/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/cs/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/cs/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/cs/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/cs/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/cs/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/cs/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/cs/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/cs/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/cs/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/cs/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/cs/plasma_calendar_alternatecalendar.po create mode 100644 po/cs/plasma_calendar_astronomicalevents.po create mode 100644 po/cs/plasma_runner_CharacterRunner.po create mode 100644 po/cs/plasma_runner_converterrunner.po create mode 100644 po/cs/plasma_runner_datetime.po create mode 100644 po/cs/plasma_runner_katesessions.po create mode 100644 po/cs/plasma_runner_konsoleprofiles.po create mode 100644 po/cs/plasma_runner_krunner_dictionary.po create mode 100644 po/cs/plasma_runner_spellcheckrunner.po create mode 100644 po/cs/plasma_wallpaper_org.kde.potd.po create mode 100644 po/csb/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/csb/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/csb/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/csb/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/da/plasma_addons_engine_dict.po create mode 100644 po/da/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/da/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/da/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/da/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/da/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/da/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/da/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/da/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/da/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/da/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/da/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/da/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/da/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/da/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/da/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/da/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/da/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/da/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/da/plasma_calendar_astronomicalevents.po create mode 100644 po/da/plasma_runner_CharacterRunner.po create mode 100644 po/da/plasma_runner_converterrunner.po create mode 100644 po/da/plasma_runner_datetime.po create mode 100644 po/da/plasma_runner_katesessions.po create mode 100644 po/da/plasma_runner_konsoleprofiles.po create mode 100644 po/da/plasma_runner_krunner_dictionary.po create mode 100644 po/da/plasma_runner_spellcheckrunner.po create mode 100644 po/da/plasma_wallpaper_org.kde.potd.po create mode 100644 po/de/kwin_effect_cube.po create mode 100644 po/de/plasma_addons_engine_dict.po create mode 100644 po/de/plasma_addons_profiles_utility.po create mode 100644 po/de/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/de/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/de/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/de/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/de/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/de/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/de/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/de/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/de/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/de/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/de/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/de/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/de/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/de/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/de/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/de/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/de/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/de/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/de/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/de/plasma_calendar_alternatecalendar.po create mode 100644 po/de/plasma_calendar_astronomicalevents.po create mode 100644 po/de/plasma_runner_CharacterRunner.po create mode 100644 po/de/plasma_runner_converterrunner.po create mode 100644 po/de/plasma_runner_datetime.po create mode 100644 po/de/plasma_runner_katesessions.po create mode 100644 po/de/plasma_runner_konsoleprofiles.po create mode 100644 po/de/plasma_runner_krunner_dictionary.po create mode 100644 po/de/plasma_runner_spellcheckrunner.po create mode 100644 po/de/plasma_wallpaper_org.kde.potd.po create mode 100644 po/el/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/el/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/el/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/el/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/el/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/el/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/el/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/el/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/el/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/el/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/el/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/el/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/el/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/el/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/el/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/el/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/el/plasma_runner_CharacterRunner.po create mode 100644 po/el/plasma_runner_converterrunner.po create mode 100644 po/el/plasma_runner_datetime.po create mode 100644 po/el/plasma_runner_katesessions.po create mode 100644 po/el/plasma_runner_krunner_dictionary.po create mode 100644 po/el/plasma_runner_spellcheckrunner.po create mode 100644 po/en_GB/kwin_effect_cube.po create mode 100644 po/en_GB/plasma_addons_engine_dict.po create mode 100644 po/en_GB/plasma_addons_profiles_utility.po create mode 100644 po/en_GB/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/en_GB/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/en_GB/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/en_GB/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/en_GB/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/en_GB/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/en_GB/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/en_GB/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/en_GB/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/en_GB/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/en_GB/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/en_GB/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/en_GB/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/en_GB/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/en_GB/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/en_GB/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/en_GB/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/en_GB/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/en_GB/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/en_GB/plasma_calendar_alternatecalendar.po create mode 100644 po/en_GB/plasma_calendar_astronomicalevents.po create mode 100644 po/en_GB/plasma_runner_CharacterRunner.po create mode 100644 po/en_GB/plasma_runner_converterrunner.po create mode 100644 po/en_GB/plasma_runner_datetime.po create mode 100644 po/en_GB/plasma_runner_katesessions.po create mode 100644 po/en_GB/plasma_runner_konsoleprofiles.po create mode 100644 po/en_GB/plasma_runner_krunner_dictionary.po create mode 100644 po/en_GB/plasma_runner_spellcheckrunner.po create mode 100644 po/en_GB/plasma_wallpaper_org.kde.potd.po create mode 100644 po/eo/kwin_effect_cube.po create mode 100644 po/eo/plasma_addons_engine_dict.po create mode 100644 po/eo/plasma_addons_profiles_utility.po create mode 100644 po/eo/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/eo/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/eo/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/eo/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/eo/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/eo/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/eo/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/eo/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/eo/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/eo/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/eo/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/eo/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/eo/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/eo/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/eo/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/eo/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/eo/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/eo/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/eo/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/eo/plasma_calendar_alternatecalendar.po create mode 100644 po/eo/plasma_calendar_astronomicalevents.po create mode 100644 po/eo/plasma_runner_CharacterRunner.po create mode 100644 po/eo/plasma_runner_converterrunner.po create mode 100644 po/eo/plasma_runner_datetime.po create mode 100644 po/eo/plasma_runner_katesessions.po create mode 100644 po/eo/plasma_runner_konsoleprofiles.po create mode 100644 po/eo/plasma_runner_krunner_dictionary.po create mode 100644 po/eo/plasma_runner_spellcheckrunner.po create mode 100644 po/eo/plasma_wallpaper_org.kde.potd.po create mode 100644 po/es/kwin_effect_cube.po create mode 100644 po/es/plasma_addons_engine_dict.po create mode 100644 po/es/plasma_addons_profiles_utility.po create mode 100644 po/es/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/es/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/es/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/es/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/es/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/es/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/es/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/es/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/es/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/es/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/es/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/es/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/es/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/es/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/es/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/es/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/es/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/es/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/es/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/es/plasma_calendar_alternatecalendar.po create mode 100644 po/es/plasma_calendar_astronomicalevents.po create mode 100644 po/es/plasma_runner_CharacterRunner.po create mode 100644 po/es/plasma_runner_converterrunner.po create mode 100644 po/es/plasma_runner_datetime.po create mode 100644 po/es/plasma_runner_katesessions.po create mode 100644 po/es/plasma_runner_konsoleprofiles.po create mode 100644 po/es/plasma_runner_krunner_dictionary.po create mode 100644 po/es/plasma_runner_spellcheckrunner.po create mode 100644 po/es/plasma_wallpaper_org.kde.potd.po create mode 100644 po/et/plasma_addons_engine_dict.po create mode 100644 po/et/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/et/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/et/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/et/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/et/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/et/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/et/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/et/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/et/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/et/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/et/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/et/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/et/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/et/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/et/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/et/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/et/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/et/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/et/plasma_calendar_astronomicalevents.po create mode 100644 po/et/plasma_runner_CharacterRunner.po create mode 100644 po/et/plasma_runner_converterrunner.po create mode 100644 po/et/plasma_runner_datetime.po create mode 100644 po/et/plasma_runner_katesessions.po create mode 100644 po/et/plasma_runner_konsoleprofiles.po create mode 100644 po/et/plasma_runner_krunner_dictionary.po create mode 100644 po/et/plasma_runner_spellcheckrunner.po create mode 100644 po/et/plasma_wallpaper_org.kde.potd.po create mode 100644 po/eu/kwin_effect_cube.po create mode 100644 po/eu/plasma_addons_engine_dict.po create mode 100644 po/eu/plasma_addons_profiles_utility.po create mode 100644 po/eu/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/eu/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/eu/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/eu/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/eu/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/eu/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/eu/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/eu/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/eu/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/eu/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/eu/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/eu/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/eu/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/eu/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/eu/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/eu/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/eu/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/eu/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/eu/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/eu/plasma_calendar_alternatecalendar.po create mode 100644 po/eu/plasma_calendar_astronomicalevents.po create mode 100644 po/eu/plasma_runner_CharacterRunner.po create mode 100644 po/eu/plasma_runner_converterrunner.po create mode 100644 po/eu/plasma_runner_datetime.po create mode 100644 po/eu/plasma_runner_katesessions.po create mode 100644 po/eu/plasma_runner_konsoleprofiles.po create mode 100644 po/eu/plasma_runner_krunner_dictionary.po create mode 100644 po/eu/plasma_runner_spellcheckrunner.po create mode 100644 po/eu/plasma_wallpaper_org.kde.potd.po create mode 100644 po/fi/kwin_effect_cube.po create mode 100644 po/fi/plasma_addons_engine_dict.po create mode 100644 po/fi/plasma_addons_profiles_utility.po create mode 100644 po/fi/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/fi/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/fi/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/fi/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/fi/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/fi/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/fi/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/fi/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/fi/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/fi/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/fi/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/fi/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/fi/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/fi/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/fi/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/fi/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/fi/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/fi/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/fi/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/fi/plasma_calendar_alternatecalendar.po create mode 100644 po/fi/plasma_calendar_astronomicalevents.po create mode 100644 po/fi/plasma_runner_CharacterRunner.po create mode 100644 po/fi/plasma_runner_converterrunner.po create mode 100644 po/fi/plasma_runner_datetime.po create mode 100644 po/fi/plasma_runner_katesessions.po create mode 100644 po/fi/plasma_runner_konsoleprofiles.po create mode 100644 po/fi/plasma_runner_krunner_dictionary.po create mode 100644 po/fi/plasma_runner_spellcheckrunner.po create mode 100644 po/fi/plasma_wallpaper_org.kde.potd.po create mode 100644 po/fr/kwin_effect_cube.po create mode 100644 po/fr/plasma_addons_engine_dict.po create mode 100644 po/fr/plasma_addons_profiles_utility.po create mode 100644 po/fr/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/fr/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/fr/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/fr/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/fr/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/fr/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/fr/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/fr/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/fr/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/fr/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/fr/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/fr/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/fr/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/fr/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/fr/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/fr/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/fr/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/fr/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/fr/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/fr/plasma_calendar_alternatecalendar.po create mode 100644 po/fr/plasma_calendar_astronomicalevents.po create mode 100644 po/fr/plasma_runner_CharacterRunner.po create mode 100644 po/fr/plasma_runner_converterrunner.po create mode 100644 po/fr/plasma_runner_datetime.po create mode 100644 po/fr/plasma_runner_katesessions.po create mode 100644 po/fr/plasma_runner_konsoleprofiles.po create mode 100644 po/fr/plasma_runner_krunner_dictionary.po create mode 100644 po/fr/plasma_runner_spellcheckrunner.po create mode 100644 po/fr/plasma_wallpaper_org.kde.potd.po create mode 100644 po/ga/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/ga/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/ga/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/ga/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/ga/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/ga/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/ga/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/ga/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/ga/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/ga/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/ga/plasma_runner_CharacterRunner.po create mode 100644 po/ga/plasma_runner_converterrunner.po create mode 100644 po/ga/plasma_runner_datetime.po create mode 100644 po/ga/plasma_runner_katesessions.po create mode 100644 po/ga/plasma_runner_krunner_dictionary.po create mode 100644 po/ga/plasma_runner_spellcheckrunner.po create mode 100644 po/gl/kwin_effect_cube.po create mode 100644 po/gl/plasma_addons_engine_dict.po create mode 100644 po/gl/plasma_addons_profiles_utility.po create mode 100644 po/gl/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/gl/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/gl/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/gl/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/gl/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/gl/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/gl/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/gl/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/gl/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/gl/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/gl/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/gl/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/gl/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/gl/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/gl/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/gl/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/gl/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/gl/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/gl/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/gl/plasma_calendar_alternatecalendar.po create mode 100644 po/gl/plasma_calendar_astronomicalevents.po create mode 100644 po/gl/plasma_runner_CharacterRunner.po create mode 100644 po/gl/plasma_runner_converterrunner.po create mode 100644 po/gl/plasma_runner_datetime.po create mode 100644 po/gl/plasma_runner_katesessions.po create mode 100644 po/gl/plasma_runner_konsoleprofiles.po create mode 100644 po/gl/plasma_runner_krunner_dictionary.po create mode 100644 po/gl/plasma_runner_spellcheckrunner.po create mode 100644 po/gl/plasma_wallpaper_org.kde.potd.po create mode 100644 po/he/kwin_effect_cube.po create mode 100644 po/he/plasma_addons_engine_dict.po create mode 100644 po/he/plasma_addons_profiles_utility.po create mode 100644 po/he/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/he/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/he/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/he/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/he/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/he/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/he/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/he/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/he/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/he/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/he/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/he/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/he/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/he/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/he/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/he/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/he/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/he/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/he/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/he/plasma_calendar_alternatecalendar.po create mode 100644 po/he/plasma_calendar_astronomicalevents.po create mode 100644 po/he/plasma_runner_CharacterRunner.po create mode 100644 po/he/plasma_runner_converterrunner.po create mode 100644 po/he/plasma_runner_datetime.po create mode 100644 po/he/plasma_runner_katesessions.po create mode 100644 po/he/plasma_runner_konsoleprofiles.po create mode 100644 po/he/plasma_runner_krunner_dictionary.po create mode 100644 po/he/plasma_runner_spellcheckrunner.po create mode 100644 po/he/plasma_wallpaper_org.kde.potd.po create mode 100644 po/hi/plasma_addons_engine_dict.po create mode 100644 po/hi/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/hi/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/hi/plasma_runner_krunner_dictionary.po create mode 100644 po/hi/plasma_runner_spellcheckrunner.po create mode 100644 po/hr/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/hr/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/hr/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/hr/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/hr/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/hr/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/hr/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/hr/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/hr/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/hr/plasma_runner_CharacterRunner.po create mode 100644 po/hr/plasma_runner_converterrunner.po create mode 100644 po/hr/plasma_runner_datetime.po create mode 100644 po/hr/plasma_runner_katesessions.po create mode 100644 po/hr/plasma_runner_spellcheckrunner.po create mode 100644 po/hu/kwin_effect_cube.po create mode 100644 po/hu/plasma_addons_engine_dict.po create mode 100644 po/hu/plasma_addons_profiles_utility.po create mode 100644 po/hu/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/hu/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/hu/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/hu/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/hu/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/hu/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/hu/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/hu/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/hu/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/hu/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/hu/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/hu/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/hu/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/hu/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/hu/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/hu/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/hu/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/hu/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/hu/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/hu/plasma_calendar_alternatecalendar.po create mode 100644 po/hu/plasma_calendar_astronomicalevents.po create mode 100644 po/hu/plasma_runner_CharacterRunner.po create mode 100644 po/hu/plasma_runner_converterrunner.po create mode 100644 po/hu/plasma_runner_datetime.po create mode 100644 po/hu/plasma_runner_katesessions.po create mode 100644 po/hu/plasma_runner_konsoleprofiles.po create mode 100644 po/hu/plasma_runner_krunner_dictionary.po create mode 100644 po/hu/plasma_runner_spellcheckrunner.po create mode 100644 po/hu/plasma_wallpaper_org.kde.potd.po create mode 100644 po/ia/kwin_effect_cube.po create mode 100644 po/ia/plasma_addons_engine_dict.po create mode 100644 po/ia/plasma_addons_profiles_utility.po create mode 100644 po/ia/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/ia/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/ia/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/ia/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/ia/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/ia/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/ia/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/ia/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/ia/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/ia/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/ia/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/ia/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/ia/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/ia/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/ia/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/ia/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/ia/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/ia/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/ia/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/ia/plasma_calendar_alternatecalendar.po create mode 100644 po/ia/plasma_calendar_astronomicalevents.po create mode 100644 po/ia/plasma_runner_CharacterRunner.po create mode 100644 po/ia/plasma_runner_converterrunner.po create mode 100644 po/ia/plasma_runner_datetime.po create mode 100644 po/ia/plasma_runner_katesessions.po create mode 100644 po/ia/plasma_runner_konsoleprofiles.po create mode 100644 po/ia/plasma_runner_krunner_dictionary.po create mode 100644 po/ia/plasma_runner_spellcheckrunner.po create mode 100644 po/ia/plasma_wallpaper_org.kde.potd.po create mode 100644 po/id/plasma_addons_engine_dict.po create mode 100644 po/id/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/id/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/id/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/id/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/id/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/id/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/id/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/id/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/id/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/id/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/id/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/id/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/id/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/id/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/id/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/id/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/id/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/id/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/id/plasma_calendar_alternatecalendar.po create mode 100644 po/id/plasma_calendar_astronomicalevents.po create mode 100644 po/id/plasma_runner_CharacterRunner.po create mode 100644 po/id/plasma_runner_converterrunner.po create mode 100644 po/id/plasma_runner_datetime.po create mode 100644 po/id/plasma_runner_katesessions.po create mode 100644 po/id/plasma_runner_konsoleprofiles.po create mode 100644 po/id/plasma_runner_krunner_dictionary.po create mode 100644 po/id/plasma_runner_spellcheckrunner.po create mode 100644 po/id/plasma_wallpaper_org.kde.potd.po create mode 100644 po/is/kwin_effect_cube.po create mode 100644 po/is/plasma_addons_engine_dict.po create mode 100644 po/is/plasma_addons_profiles_utility.po create mode 100644 po/is/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/is/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/is/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/is/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/is/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/is/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/is/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/is/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/is/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/is/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/is/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/is/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/is/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/is/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/is/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/is/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/is/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/is/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/is/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/is/plasma_calendar_alternatecalendar.po create mode 100644 po/is/plasma_calendar_astronomicalevents.po create mode 100644 po/is/plasma_runner_CharacterRunner.po create mode 100644 po/is/plasma_runner_converterrunner.po create mode 100644 po/is/plasma_runner_datetime.po create mode 100644 po/is/plasma_runner_katesessions.po create mode 100644 po/is/plasma_runner_konsoleprofiles.po create mode 100644 po/is/plasma_runner_krunner_dictionary.po create mode 100644 po/is/plasma_runner_spellcheckrunner.po create mode 100644 po/is/plasma_wallpaper_org.kde.potd.po create mode 100644 po/it/kwin_effect_cube.po create mode 100644 po/it/plasma_addons_engine_dict.po create mode 100644 po/it/plasma_addons_profiles_utility.po create mode 100644 po/it/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/it/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/it/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/it/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/it/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/it/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/it/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/it/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/it/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/it/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/it/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/it/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/it/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/it/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/it/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/it/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/it/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/it/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/it/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/it/plasma_calendar_alternatecalendar.po create mode 100644 po/it/plasma_calendar_astronomicalevents.po create mode 100644 po/it/plasma_runner_CharacterRunner.po create mode 100644 po/it/plasma_runner_converterrunner.po create mode 100644 po/it/plasma_runner_datetime.po create mode 100644 po/it/plasma_runner_katesessions.po create mode 100644 po/it/plasma_runner_konsoleprofiles.po create mode 100644 po/it/plasma_runner_krunner_dictionary.po create mode 100644 po/it/plasma_runner_spellcheckrunner.po create mode 100644 po/it/plasma_wallpaper_org.kde.potd.po create mode 100644 po/ja/kwin_effect_cube.po create mode 100644 po/ja/plasma_addons_engine_dict.po create mode 100644 po/ja/plasma_addons_profiles_utility.po create mode 100644 po/ja/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/ja/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/ja/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/ja/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/ja/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/ja/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/ja/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/ja/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/ja/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/ja/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/ja/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/ja/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/ja/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/ja/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/ja/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/ja/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/ja/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/ja/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/ja/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/ja/plasma_calendar_alternatecalendar.po create mode 100644 po/ja/plasma_calendar_astronomicalevents.po create mode 100644 po/ja/plasma_runner_CharacterRunner.po create mode 100644 po/ja/plasma_runner_converterrunner.po create mode 100644 po/ja/plasma_runner_datetime.po create mode 100644 po/ja/plasma_runner_katesessions.po create mode 100644 po/ja/plasma_runner_konsoleprofiles.po create mode 100644 po/ja/plasma_runner_krunner_dictionary.po create mode 100644 po/ja/plasma_runner_spellcheckrunner.po create mode 100644 po/ja/plasma_wallpaper_org.kde.potd.po create mode 100644 po/ka/kwin_effect_cube.po create mode 100644 po/ka/plasma_addons_engine_dict.po create mode 100644 po/ka/plasma_addons_profiles_utility.po create mode 100644 po/ka/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/ka/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/ka/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/ka/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/ka/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/ka/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/ka/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/ka/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/ka/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/ka/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/ka/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/ka/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/ka/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/ka/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/ka/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/ka/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/ka/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/ka/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/ka/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/ka/plasma_calendar_alternatecalendar.po create mode 100644 po/ka/plasma_calendar_astronomicalevents.po create mode 100644 po/ka/plasma_runner_CharacterRunner.po create mode 100644 po/ka/plasma_runner_converterrunner.po create mode 100644 po/ka/plasma_runner_datetime.po create mode 100644 po/ka/plasma_runner_katesessions.po create mode 100644 po/ka/plasma_runner_konsoleprofiles.po create mode 100644 po/ka/plasma_runner_krunner_dictionary.po create mode 100644 po/ka/plasma_runner_spellcheckrunner.po create mode 100644 po/ka/plasma_wallpaper_org.kde.potd.po create mode 100644 po/kk/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/kk/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/kk/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/kk/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/kk/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/kk/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/kk/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/kk/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/kk/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/kk/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/kk/plasma_runner_CharacterRunner.po create mode 100644 po/kk/plasma_runner_converterrunner.po create mode 100644 po/kk/plasma_runner_datetime.po create mode 100644 po/kk/plasma_runner_katesessions.po create mode 100644 po/kk/plasma_runner_krunner_dictionary.po create mode 100644 po/kk/plasma_runner_spellcheckrunner.po create mode 100644 po/km/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/km/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/km/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/km/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/km/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/km/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/km/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/km/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/km/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/km/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/km/plasma_runner_CharacterRunner.po create mode 100644 po/km/plasma_runner_converterrunner.po create mode 100644 po/km/plasma_runner_datetime.po create mode 100644 po/km/plasma_runner_katesessions.po create mode 100644 po/km/plasma_runner_krunner_dictionary.po create mode 100644 po/km/plasma_runner_spellcheckrunner.po create mode 100644 po/ko/kwin_effect_cube.po create mode 100644 po/ko/plasma_addons_engine_dict.po create mode 100644 po/ko/plasma_addons_profiles_utility.po create mode 100644 po/ko/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/ko/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/ko/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/ko/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/ko/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/ko/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/ko/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/ko/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/ko/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/ko/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/ko/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/ko/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/ko/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/ko/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/ko/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/ko/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/ko/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/ko/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/ko/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/ko/plasma_calendar_alternatecalendar.po create mode 100644 po/ko/plasma_calendar_astronomicalevents.po create mode 100644 po/ko/plasma_runner_CharacterRunner.po create mode 100644 po/ko/plasma_runner_converterrunner.po create mode 100644 po/ko/plasma_runner_datetime.po create mode 100644 po/ko/plasma_runner_katesessions.po create mode 100644 po/ko/plasma_runner_konsoleprofiles.po create mode 100644 po/ko/plasma_runner_krunner_dictionary.po create mode 100644 po/ko/plasma_runner_spellcheckrunner.po create mode 100644 po/ko/plasma_wallpaper_org.kde.potd.po create mode 100644 po/ku/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/ku/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/ku/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/ku/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/ku/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/ku/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/ku/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/ku/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/ku/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/ku/plasma_runner_converterrunner.po create mode 100644 po/ku/plasma_runner_katesessions.po create mode 100644 po/ku/plasma_runner_spellcheckrunner.po create mode 100644 po/lt/kwin_effect_cube.po create mode 100644 po/lt/plasma_addons_engine_dict.po create mode 100644 po/lt/plasma_addons_profiles_utility.po create mode 100644 po/lt/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/lt/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/lt/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/lt/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/lt/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/lt/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/lt/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/lt/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/lt/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/lt/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/lt/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/lt/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/lt/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/lt/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/lt/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/lt/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/lt/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/lt/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/lt/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/lt/plasma_calendar_alternatecalendar.po create mode 100644 po/lt/plasma_calendar_astronomicalevents.po create mode 100644 po/lt/plasma_runner_CharacterRunner.po create mode 100644 po/lt/plasma_runner_converterrunner.po create mode 100644 po/lt/plasma_runner_datetime.po create mode 100644 po/lt/plasma_runner_katesessions.po create mode 100644 po/lt/plasma_runner_konsoleprofiles.po create mode 100644 po/lt/plasma_runner_krunner_dictionary.po create mode 100644 po/lt/plasma_runner_spellcheckrunner.po create mode 100644 po/lt/plasma_wallpaper_org.kde.potd.po create mode 100644 po/lv/kwin_effect_cube.po create mode 100644 po/lv/plasma_addons_engine_dict.po create mode 100644 po/lv/plasma_addons_profiles_utility.po create mode 100644 po/lv/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/lv/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/lv/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/lv/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/lv/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/lv/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/lv/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/lv/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/lv/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/lv/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/lv/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/lv/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/lv/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/lv/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/lv/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/lv/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/lv/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/lv/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/lv/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/lv/plasma_calendar_alternatecalendar.po create mode 100644 po/lv/plasma_calendar_astronomicalevents.po create mode 100644 po/lv/plasma_runner_CharacterRunner.po create mode 100644 po/lv/plasma_runner_converterrunner.po create mode 100644 po/lv/plasma_runner_datetime.po create mode 100644 po/lv/plasma_runner_katesessions.po create mode 100644 po/lv/plasma_runner_konsoleprofiles.po create mode 100644 po/lv/plasma_runner_krunner_dictionary.po create mode 100644 po/lv/plasma_runner_spellcheckrunner.po create mode 100644 po/lv/plasma_wallpaper_org.kde.potd.po create mode 100644 po/ml/plasma_addons_engine_dict.po create mode 100644 po/ml/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/ml/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/ml/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/ml/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/ml/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/ml/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/ml/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/ml/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/ml/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/ml/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/ml/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/ml/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/ml/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/ml/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/ml/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/ml/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/ml/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/ml/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/ml/plasma_calendar_astronomicalevents.po create mode 100644 po/ml/plasma_runner_CharacterRunner.po create mode 100644 po/ml/plasma_runner_converterrunner.po create mode 100644 po/ml/plasma_runner_datetime.po create mode 100644 po/ml/plasma_runner_katesessions.po create mode 100644 po/ml/plasma_runner_konsoleprofiles.po create mode 100644 po/ml/plasma_runner_krunner_dictionary.po create mode 100644 po/ml/plasma_runner_spellcheckrunner.po create mode 100644 po/mr/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/mr/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/mr/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/mr/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/mr/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/mr/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/mr/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/mr/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/mr/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/mr/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/mr/plasma_runner_CharacterRunner.po create mode 100644 po/mr/plasma_runner_converterrunner.po create mode 100644 po/mr/plasma_runner_datetime.po create mode 100644 po/mr/plasma_runner_katesessions.po create mode 100644 po/mr/plasma_runner_krunner_dictionary.po create mode 100644 po/mr/plasma_runner_spellcheckrunner.po create mode 100644 po/ms/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/ms/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/ms/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/ms/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/ms/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/my/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/nb/plasma_addons_engine_dict.po create mode 100644 po/nb/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/nb/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/nb/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/nb/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/nb/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/nb/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/nb/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/nb/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/nb/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/nb/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/nb/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/nb/plasma_calendar_astronomicalevents.po create mode 100644 po/nb/plasma_runner_CharacterRunner.po create mode 100644 po/nb/plasma_runner_converterrunner.po create mode 100644 po/nb/plasma_runner_datetime.po create mode 100644 po/nb/plasma_runner_katesessions.po create mode 100644 po/nb/plasma_runner_konsoleprofiles.po create mode 100644 po/nb/plasma_runner_krunner_dictionary.po create mode 100644 po/nb/plasma_runner_spellcheckrunner.po create mode 100644 po/nds/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/nds/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/nds/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/nds/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/nds/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/nds/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/nds/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/nds/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/nds/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/nds/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/nds/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/nds/plasma_runner_CharacterRunner.po create mode 100644 po/nds/plasma_runner_converterrunner.po create mode 100644 po/nds/plasma_runner_datetime.po create mode 100644 po/nds/plasma_runner_katesessions.po create mode 100644 po/nds/plasma_runner_krunner_dictionary.po create mode 100644 po/nds/plasma_runner_spellcheckrunner.po create mode 100644 po/nl/kwin_effect_cube.po create mode 100644 po/nl/plasma_addons_engine_dict.po create mode 100644 po/nl/plasma_addons_profiles_utility.po create mode 100644 po/nl/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/nl/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/nl/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/nl/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/nl/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/nl/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/nl/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/nl/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/nl/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/nl/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/nl/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/nl/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/nl/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/nl/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/nl/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/nl/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/nl/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/nl/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/nl/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/nl/plasma_calendar_alternatecalendar.po create mode 100644 po/nl/plasma_calendar_astronomicalevents.po create mode 100644 po/nl/plasma_runner_CharacterRunner.po create mode 100644 po/nl/plasma_runner_converterrunner.po create mode 100644 po/nl/plasma_runner_datetime.po create mode 100644 po/nl/plasma_runner_katesessions.po create mode 100644 po/nl/plasma_runner_konsoleprofiles.po create mode 100644 po/nl/plasma_runner_krunner_dictionary.po create mode 100644 po/nl/plasma_runner_spellcheckrunner.po create mode 100644 po/nl/plasma_wallpaper_org.kde.potd.po create mode 100644 po/nn/kwin_effect_cube.po create mode 100644 po/nn/plasma_addons_engine_dict.po create mode 100644 po/nn/plasma_addons_profiles_utility.po create mode 100644 po/nn/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/nn/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/nn/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/nn/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/nn/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/nn/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/nn/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/nn/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/nn/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/nn/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/nn/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/nn/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/nn/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/nn/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/nn/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/nn/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/nn/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/nn/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/nn/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/nn/plasma_calendar_alternatecalendar.po create mode 100644 po/nn/plasma_calendar_astronomicalevents.po create mode 100644 po/nn/plasma_runner_CharacterRunner.po create mode 100644 po/nn/plasma_runner_converterrunner.po create mode 100644 po/nn/plasma_runner_datetime.po create mode 100644 po/nn/plasma_runner_katesessions.po create mode 100644 po/nn/plasma_runner_konsoleprofiles.po create mode 100644 po/nn/plasma_runner_krunner_dictionary.po create mode 100644 po/nn/plasma_runner_spellcheckrunner.po create mode 100644 po/nn/plasma_wallpaper_org.kde.potd.po create mode 100644 po/pa/plasma_addons_engine_dict.po create mode 100644 po/pa/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/pa/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/pa/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/pa/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/pa/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/pa/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/pa/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/pa/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/pa/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/pa/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/pa/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/pa/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/pa/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/pa/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/pa/plasma_runner_CharacterRunner.po create mode 100644 po/pa/plasma_runner_converterrunner.po create mode 100644 po/pa/plasma_runner_datetime.po create mode 100644 po/pa/plasma_runner_katesessions.po create mode 100644 po/pa/plasma_runner_krunner_dictionary.po create mode 100644 po/pa/plasma_runner_spellcheckrunner.po create mode 100644 po/pl/kwin_effect_cube.po create mode 100644 po/pl/plasma_addons_engine_dict.po create mode 100644 po/pl/plasma_addons_profiles_utility.po create mode 100644 po/pl/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/pl/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/pl/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/pl/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/pl/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/pl/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/pl/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/pl/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/pl/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/pl/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/pl/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/pl/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/pl/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/pl/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/pl/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/pl/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/pl/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/pl/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/pl/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/pl/plasma_calendar_alternatecalendar.po create mode 100644 po/pl/plasma_calendar_astronomicalevents.po create mode 100644 po/pl/plasma_runner_CharacterRunner.po create mode 100644 po/pl/plasma_runner_converterrunner.po create mode 100644 po/pl/plasma_runner_datetime.po create mode 100644 po/pl/plasma_runner_katesessions.po create mode 100644 po/pl/plasma_runner_konsoleprofiles.po create mode 100644 po/pl/plasma_runner_krunner_dictionary.po create mode 100644 po/pl/plasma_runner_spellcheckrunner.po create mode 100644 po/pl/plasma_wallpaper_org.kde.potd.po create mode 100644 po/pt/plasma_addons_engine_dict.po create mode 100644 po/pt/plasma_addons_profiles_utility.po create mode 100644 po/pt/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/pt/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/pt/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/pt/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/pt/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/pt/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/pt/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/pt/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/pt/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/pt/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/pt/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/pt/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/pt/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/pt/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/pt/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/pt/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/pt/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/pt/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/pt/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/pt/plasma_calendar_alternatecalendar.po create mode 100644 po/pt/plasma_calendar_astronomicalevents.po create mode 100644 po/pt/plasma_runner_CharacterRunner.po create mode 100644 po/pt/plasma_runner_converterrunner.po create mode 100644 po/pt/plasma_runner_datetime.po create mode 100644 po/pt/plasma_runner_katesessions.po create mode 100644 po/pt/plasma_runner_konsoleprofiles.po create mode 100644 po/pt/plasma_runner_krunner_dictionary.po create mode 100644 po/pt/plasma_runner_spellcheckrunner.po create mode 100644 po/pt/plasma_wallpaper_org.kde.potd.po create mode 100644 po/pt_BR/kwin_effect_cube.po create mode 100644 po/pt_BR/plasma_addons_engine_dict.po create mode 100644 po/pt_BR/plasma_addons_profiles_utility.po create mode 100644 po/pt_BR/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/pt_BR/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/pt_BR/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/pt_BR/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/pt_BR/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/pt_BR/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/pt_BR/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/pt_BR/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/pt_BR/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/pt_BR/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/pt_BR/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/pt_BR/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/pt_BR/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/pt_BR/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/pt_BR/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/pt_BR/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/pt_BR/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/pt_BR/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/pt_BR/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/pt_BR/plasma_calendar_alternatecalendar.po create mode 100644 po/pt_BR/plasma_calendar_astronomicalevents.po create mode 100644 po/pt_BR/plasma_runner_CharacterRunner.po create mode 100644 po/pt_BR/plasma_runner_converterrunner.po create mode 100644 po/pt_BR/plasma_runner_datetime.po create mode 100644 po/pt_BR/plasma_runner_katesessions.po create mode 100644 po/pt_BR/plasma_runner_konsoleprofiles.po create mode 100644 po/pt_BR/plasma_runner_krunner_dictionary.po create mode 100644 po/pt_BR/plasma_runner_spellcheckrunner.po create mode 100644 po/pt_BR/plasma_wallpaper_org.kde.potd.po create mode 100644 po/ro/kwin_effect_cube.po create mode 100644 po/ro/plasma_addons_engine_dict.po create mode 100644 po/ro/plasma_addons_profiles_utility.po create mode 100644 po/ro/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/ro/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/ro/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/ro/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/ro/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/ro/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/ro/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/ro/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/ro/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/ro/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/ro/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/ro/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/ro/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/ro/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/ro/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/ro/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/ro/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/ro/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/ro/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/ro/plasma_calendar_alternatecalendar.po create mode 100644 po/ro/plasma_calendar_astronomicalevents.po create mode 100644 po/ro/plasma_runner_CharacterRunner.po create mode 100644 po/ro/plasma_runner_converterrunner.po create mode 100644 po/ro/plasma_runner_datetime.po create mode 100644 po/ro/plasma_runner_katesessions.po create mode 100644 po/ro/plasma_runner_konsoleprofiles.po create mode 100644 po/ro/plasma_runner_krunner_dictionary.po create mode 100644 po/ro/plasma_runner_spellcheckrunner.po create mode 100644 po/ro/plasma_wallpaper_org.kde.potd.po create mode 100644 po/ru/kwin_effect_cube.po create mode 100644 po/ru/plasma_addons_engine_dict.po create mode 100644 po/ru/plasma_addons_profiles_utility.po create mode 100644 po/ru/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/ru/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/ru/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/ru/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/ru/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/ru/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/ru/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/ru/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/ru/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/ru/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/ru/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/ru/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/ru/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/ru/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/ru/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/ru/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/ru/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/ru/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/ru/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/ru/plasma_calendar_alternatecalendar.po create mode 100644 po/ru/plasma_calendar_astronomicalevents.po create mode 100644 po/ru/plasma_runner_CharacterRunner.po create mode 100644 po/ru/plasma_runner_converterrunner.po create mode 100644 po/ru/plasma_runner_datetime.po create mode 100644 po/ru/plasma_runner_katesessions.po create mode 100644 po/ru/plasma_runner_konsoleprofiles.po create mode 100644 po/ru/plasma_runner_krunner_dictionary.po create mode 100644 po/ru/plasma_runner_spellcheckrunner.po create mode 100644 po/ru/plasma_wallpaper_org.kde.potd.po create mode 100644 po/sk/kwin_effect_cube.po create mode 100644 po/sk/plasma_addons_engine_dict.po create mode 100644 po/sk/plasma_addons_profiles_utility.po create mode 100644 po/sk/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/sk/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/sk/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/sk/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/sk/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/sk/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/sk/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/sk/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/sk/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/sk/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/sk/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/sk/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/sk/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/sk/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/sk/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/sk/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/sk/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/sk/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/sk/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/sk/plasma_calendar_alternatecalendar.po create mode 100644 po/sk/plasma_calendar_astronomicalevents.po create mode 100644 po/sk/plasma_runner_CharacterRunner.po create mode 100644 po/sk/plasma_runner_converterrunner.po create mode 100644 po/sk/plasma_runner_datetime.po create mode 100644 po/sk/plasma_runner_katesessions.po create mode 100644 po/sk/plasma_runner_konsoleprofiles.po create mode 100644 po/sk/plasma_runner_krunner_dictionary.po create mode 100644 po/sk/plasma_runner_spellcheckrunner.po create mode 100644 po/sk/plasma_wallpaper_org.kde.potd.po create mode 100644 po/sl/kwin_effect_cube.po create mode 100644 po/sl/plasma_addons_engine_dict.po create mode 100644 po/sl/plasma_addons_profiles_utility.po create mode 100644 po/sl/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/sl/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/sl/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/sl/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/sl/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/sl/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/sl/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/sl/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/sl/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/sl/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/sl/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/sl/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/sl/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/sl/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/sl/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/sl/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/sl/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/sl/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/sl/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/sl/plasma_calendar_alternatecalendar.po create mode 100644 po/sl/plasma_calendar_astronomicalevents.po create mode 100644 po/sl/plasma_runner_CharacterRunner.po create mode 100644 po/sl/plasma_runner_converterrunner.po create mode 100644 po/sl/plasma_runner_datetime.po create mode 100644 po/sl/plasma_runner_katesessions.po create mode 100644 po/sl/plasma_runner_konsoleprofiles.po create mode 100644 po/sl/plasma_runner_krunner_dictionary.po create mode 100644 po/sl/plasma_runner_spellcheckrunner.po create mode 100644 po/sl/plasma_wallpaper_org.kde.potd.po create mode 100644 po/sq/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/sq/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/sq/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/sq/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/sq/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/sq/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/sq/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/sq/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/sq/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/sq/plasma_runner_converterrunner.po create mode 100644 po/sq/plasma_runner_katesessions.po create mode 100644 po/sq/plasma_runner_spellcheckrunner.po create mode 100644 po/sr/plasma_addons_engine_dict.po create mode 100644 po/sr/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/sr/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/sr/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/sr/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/sr/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/sr/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/sr/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/sr/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/sr/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/sr/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/sr/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/sr/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/sr/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/sr/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/sr/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/sr/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/sr/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/sr/plasma_runner_CharacterRunner.po create mode 100644 po/sr/plasma_runner_converterrunner.po create mode 100644 po/sr/plasma_runner_datetime.po create mode 100644 po/sr/plasma_runner_katesessions.po create mode 100644 po/sr/plasma_runner_krunner_dictionary.po create mode 100644 po/sr/plasma_runner_spellcheckrunner.po create mode 100644 po/sr@ijekavian/plasma_addons_engine_dict.po create mode 100644 po/sr@ijekavian/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/sr@ijekavian/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/sr@ijekavian/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/sr@ijekavian/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/sr@ijekavian/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/sr@ijekavian/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/sr@ijekavian/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/sr@ijekavian/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/sr@ijekavian/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/sr@ijekavian/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/sr@ijekavian/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/sr@ijekavian/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/sr@ijekavian/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/sr@ijekavian/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/sr@ijekavian/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/sr@ijekavian/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/sr@ijekavian/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/sr@ijekavian/plasma_runner_CharacterRunner.po create mode 100644 po/sr@ijekavian/plasma_runner_converterrunner.po create mode 100644 po/sr@ijekavian/plasma_runner_datetime.po create mode 100644 po/sr@ijekavian/plasma_runner_katesessions.po create mode 100644 po/sr@ijekavian/plasma_runner_krunner_dictionary.po create mode 100644 po/sr@ijekavian/plasma_runner_spellcheckrunner.po create mode 100644 po/sr@ijekavianlatin/plasma_addons_engine_dict.po create mode 100644 po/sr@ijekavianlatin/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/sr@ijekavianlatin/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/sr@ijekavianlatin/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/sr@ijekavianlatin/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/sr@ijekavianlatin/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/sr@ijekavianlatin/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/sr@ijekavianlatin/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/sr@ijekavianlatin/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/sr@ijekavianlatin/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/sr@ijekavianlatin/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/sr@ijekavianlatin/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/sr@ijekavianlatin/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/sr@ijekavianlatin/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/sr@ijekavianlatin/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/sr@ijekavianlatin/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/sr@ijekavianlatin/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/sr@ijekavianlatin/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/sr@ijekavianlatin/plasma_runner_CharacterRunner.po create mode 100644 po/sr@ijekavianlatin/plasma_runner_converterrunner.po create mode 100644 po/sr@ijekavianlatin/plasma_runner_datetime.po create mode 100644 po/sr@ijekavianlatin/plasma_runner_katesessions.po create mode 100644 po/sr@ijekavianlatin/plasma_runner_krunner_dictionary.po create mode 100644 po/sr@ijekavianlatin/plasma_runner_spellcheckrunner.po create mode 100644 po/sr@latin/plasma_addons_engine_dict.po create mode 100644 po/sr@latin/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/sr@latin/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/sr@latin/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/sr@latin/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/sr@latin/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/sr@latin/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/sr@latin/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/sr@latin/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/sr@latin/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/sr@latin/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/sr@latin/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/sr@latin/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/sr@latin/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/sr@latin/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/sr@latin/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/sr@latin/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/sr@latin/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/sr@latin/plasma_runner_CharacterRunner.po create mode 100644 po/sr@latin/plasma_runner_converterrunner.po create mode 100644 po/sr@latin/plasma_runner_datetime.po create mode 100644 po/sr@latin/plasma_runner_katesessions.po create mode 100644 po/sr@latin/plasma_runner_krunner_dictionary.po create mode 100644 po/sr@latin/plasma_runner_spellcheckrunner.po create mode 100644 po/sv/kwin_effect_cube.po create mode 100644 po/sv/plasma_addons_engine_dict.po create mode 100644 po/sv/plasma_addons_profiles_utility.po create mode 100644 po/sv/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/sv/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/sv/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/sv/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/sv/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/sv/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/sv/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/sv/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/sv/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/sv/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/sv/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/sv/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/sv/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/sv/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/sv/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/sv/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/sv/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/sv/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/sv/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/sv/plasma_calendar_alternatecalendar.po create mode 100644 po/sv/plasma_calendar_astronomicalevents.po create mode 100644 po/sv/plasma_runner_CharacterRunner.po create mode 100644 po/sv/plasma_runner_converterrunner.po create mode 100644 po/sv/plasma_runner_datetime.po create mode 100644 po/sv/plasma_runner_katesessions.po create mode 100644 po/sv/plasma_runner_konsoleprofiles.po create mode 100644 po/sv/plasma_runner_krunner_dictionary.po create mode 100644 po/sv/plasma_runner_spellcheckrunner.po create mode 100644 po/sv/plasma_wallpaper_org.kde.potd.po create mode 100644 po/ta/kwin_effect_cube.po create mode 100644 po/ta/plasma_addons_engine_dict.po create mode 100644 po/ta/plasma_addons_profiles_utility.po create mode 100644 po/ta/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/ta/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/ta/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/ta/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/ta/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/ta/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/ta/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/ta/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/ta/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/ta/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/ta/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/ta/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/ta/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/ta/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/ta/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/ta/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/ta/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/ta/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/ta/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/ta/plasma_calendar_alternatecalendar.po create mode 100644 po/ta/plasma_calendar_astronomicalevents.po create mode 100644 po/ta/plasma_runner_CharacterRunner.po create mode 100644 po/ta/plasma_runner_converterrunner.po create mode 100644 po/ta/plasma_runner_datetime.po create mode 100644 po/ta/plasma_runner_katesessions.po create mode 100644 po/ta/plasma_runner_konsoleprofiles.po create mode 100644 po/ta/plasma_runner_krunner_dictionary.po create mode 100644 po/ta/plasma_runner_spellcheckrunner.po create mode 100644 po/ta/plasma_wallpaper_org.kde.potd.po create mode 100644 po/tg/plasma_addons_engine_dict.po create mode 100644 po/tg/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/tg/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/tg/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/tg/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/tg/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/tg/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/tg/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/tg/plasma_calendar_astronomicalevents.po create mode 100644 po/tg/plasma_runner_converterrunner.po create mode 100644 po/tg/plasma_runner_datetime.po create mode 100644 po/tg/plasma_runner_katesessions.po create mode 100644 po/tg/plasma_runner_konsoleprofiles.po create mode 100644 po/tg/plasma_runner_krunner_dictionary.po create mode 100644 po/tg/plasma_wallpaper_org.kde.potd.po create mode 100644 po/th/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/th/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/th/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/th/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/th/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/th/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/th/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/th/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/th/plasma_runner_katesessions.po create mode 100644 po/th/plasma_runner_krunner_dictionary.po create mode 100644 po/th/plasma_runner_spellcheckrunner.po create mode 100644 po/tok/plasma_addons_engine_dict.po create mode 100644 po/tr/kwin_effect_cube.po create mode 100644 po/tr/plasma_addons_engine_dict.po create mode 100644 po/tr/plasma_addons_profiles_utility.po create mode 100644 po/tr/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/tr/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/tr/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/tr/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/tr/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/tr/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/tr/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/tr/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/tr/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/tr/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/tr/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/tr/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/tr/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/tr/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/tr/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/tr/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/tr/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/tr/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/tr/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/tr/plasma_calendar_alternatecalendar.po create mode 100644 po/tr/plasma_calendar_astronomicalevents.po create mode 100644 po/tr/plasma_runner_CharacterRunner.po create mode 100644 po/tr/plasma_runner_converterrunner.po create mode 100644 po/tr/plasma_runner_datetime.po create mode 100644 po/tr/plasma_runner_katesessions.po create mode 100644 po/tr/plasma_runner_konsoleprofiles.po create mode 100644 po/tr/plasma_runner_krunner_dictionary.po create mode 100644 po/tr/plasma_runner_spellcheckrunner.po create mode 100644 po/tr/plasma_wallpaper_org.kde.potd.po create mode 100644 po/ug/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/ug/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/ug/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/ug/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/ug/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/ug/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/ug/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/ug/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/ug/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/ug/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/ug/plasma_runner_CharacterRunner.po create mode 100644 po/ug/plasma_runner_converterrunner.po create mode 100644 po/ug/plasma_runner_datetime.po create mode 100644 po/ug/plasma_runner_katesessions.po create mode 100644 po/ug/plasma_runner_krunner_dictionary.po create mode 100644 po/ug/plasma_runner_spellcheckrunner.po create mode 100644 po/uk/kwin_effect_cube.po create mode 100644 po/uk/plasma_addons_engine_dict.po create mode 100644 po/uk/plasma_addons_profiles_utility.po create mode 100644 po/uk/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/uk/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/uk/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/uk/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/uk/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/uk/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/uk/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/uk/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/uk/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/uk/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/uk/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/uk/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/uk/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/uk/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/uk/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/uk/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/uk/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/uk/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/uk/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/uk/plasma_calendar_alternatecalendar.po create mode 100644 po/uk/plasma_calendar_astronomicalevents.po create mode 100644 po/uk/plasma_runner_CharacterRunner.po create mode 100644 po/uk/plasma_runner_converterrunner.po create mode 100644 po/uk/plasma_runner_datetime.po create mode 100644 po/uk/plasma_runner_katesessions.po create mode 100644 po/uk/plasma_runner_konsoleprofiles.po create mode 100644 po/uk/plasma_runner_krunner_dictionary.po create mode 100644 po/uk/plasma_runner_spellcheckrunner.po create mode 100644 po/uk/plasma_wallpaper_org.kde.potd.po create mode 100644 po/vi/kwin_effect_cube.po create mode 100644 po/vi/plasma_addons_engine_dict.po create mode 100644 po/vi/plasma_addons_profiles_utility.po create mode 100644 po/vi/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/vi/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/vi/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/vi/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/vi/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/vi/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/vi/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/vi/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/vi/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/vi/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/vi/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/vi/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/vi/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/vi/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/vi/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/vi/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/vi/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/vi/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/vi/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/vi/plasma_calendar_alternatecalendar.po create mode 100644 po/vi/plasma_calendar_astronomicalevents.po create mode 100644 po/vi/plasma_runner_CharacterRunner.po create mode 100644 po/vi/plasma_runner_converterrunner.po create mode 100644 po/vi/plasma_runner_datetime.po create mode 100644 po/vi/plasma_runner_katesessions.po create mode 100644 po/vi/plasma_runner_konsoleprofiles.po create mode 100644 po/vi/plasma_runner_krunner_dictionary.po create mode 100644 po/vi/plasma_runner_spellcheckrunner.po create mode 100644 po/vi/plasma_wallpaper_org.kde.potd.po create mode 100644 po/wa/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/wa/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/wa/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/wa/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/wa/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/wa/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/wa/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/wa/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/wa/plasma_runner_converterrunner.po create mode 100644 po/wa/plasma_runner_katesessions.po create mode 100644 po/wa/plasma_runner_spellcheckrunner.po create mode 100644 po/zh_CN/kwin_effect_cube.po create mode 100644 po/zh_CN/plasma_addons_engine_dict.po create mode 100644 po/zh_CN/plasma_addons_profiles_utility.po create mode 100644 po/zh_CN/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/zh_CN/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/zh_CN/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/zh_CN/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/zh_CN/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/zh_CN/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/zh_CN/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/zh_CN/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/zh_CN/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/zh_CN/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/zh_CN/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/zh_CN/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/zh_CN/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/zh_CN/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/zh_CN/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/zh_CN/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/zh_CN/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/zh_CN/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/zh_CN/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/zh_CN/plasma_calendar_alternatecalendar.po create mode 100644 po/zh_CN/plasma_calendar_astronomicalevents.po create mode 100644 po/zh_CN/plasma_runner_CharacterRunner.po create mode 100644 po/zh_CN/plasma_runner_converterrunner.po create mode 100644 po/zh_CN/plasma_runner_datetime.po create mode 100644 po/zh_CN/plasma_runner_katesessions.po create mode 100644 po/zh_CN/plasma_runner_konsoleprofiles.po create mode 100644 po/zh_CN/plasma_runner_krunner_dictionary.po create mode 100644 po/zh_CN/plasma_runner_spellcheckrunner.po create mode 100644 po/zh_CN/plasma_wallpaper_org.kde.potd.po create mode 100644 po/zh_TW/kwin_effect_cube.po create mode 100644 po/zh_TW/plasma_addons_engine_dict.po create mode 100644 po/zh_TW/plasma_addons_profiles_utility.po create mode 100644 po/zh_TW/plasma_applet_org.kde.plasma.addons.katesessions.po create mode 100644 po/zh_TW/plasma_applet_org.kde.plasma.binaryclock.po create mode 100644 po/zh_TW/plasma_applet_org.kde.plasma.calculator.po create mode 100644 po/zh_TW/plasma_applet_org.kde.plasma.colorpicker.po create mode 100644 po/zh_TW/plasma_applet_org.kde.plasma.comic.po create mode 100644 po/zh_TW/plasma_applet_org.kde.plasma.diskquota.po create mode 100644 po/zh_TW/plasma_applet_org.kde.plasma.fifteenpuzzle.po create mode 100644 po/zh_TW/plasma_applet_org.kde.plasma.fuzzyclock.po create mode 100644 po/zh_TW/plasma_applet_org.kde.plasma.keyboardindicator.po create mode 100644 po/zh_TW/plasma_applet_org.kde.plasma.konsoleprofiles.po create mode 100644 po/zh_TW/plasma_applet_org.kde.plasma.mediaframe.po create mode 100644 po/zh_TW/plasma_applet_org.kde.plasma.notes.po create mode 100644 po/zh_TW/plasma_applet_org.kde.plasma.private.grouping.po create mode 100644 po/zh_TW/plasma_applet_org.kde.plasma.quicklaunch.po create mode 100644 po/zh_TW/plasma_applet_org.kde.plasma.timer.po create mode 100644 po/zh_TW/plasma_applet_org.kde.plasma.userswitcher.po create mode 100644 po/zh_TW/plasma_applet_org.kde.plasma.weather.po create mode 100644 po/zh_TW/plasma_applet_org.kde.plasma.webbrowser.po create mode 100644 po/zh_TW/plasma_applet_org.kde.plasma_applet_dict.po create mode 100644 po/zh_TW/plasma_calendar_alternatecalendar.po create mode 100644 po/zh_TW/plasma_calendar_astronomicalevents.po create mode 100644 po/zh_TW/plasma_runner_CharacterRunner.po create mode 100644 po/zh_TW/plasma_runner_converterrunner.po create mode 100644 po/zh_TW/plasma_runner_datetime.po create mode 100644 po/zh_TW/plasma_runner_katesessions.po create mode 100644 po/zh_TW/plasma_runner_konsoleprofiles.po create mode 100644 po/zh_TW/plasma_runner_krunner_dictionary.po create mode 100644 po/zh_TW/plasma_runner_spellcheckrunner.po create mode 100644 po/zh_TW/plasma_wallpaper_org.kde.potd.po create mode 100644 profiles/CMakeLists.txt create mode 100644 profiles/Messages.sh create mode 100644 profiles/profilesmodel.cpp create mode 100644 profiles/profilesmodel.h create mode 100644 profiles/profilesplugin.cpp create mode 100644 runners/CMakeLists.txt create mode 100644 runners/characters/CMakeLists.txt create mode 100755 runners/characters/Messages.sh create mode 100644 runners/characters/charrunner.cpp create mode 100644 runners/characters/charrunner.h create mode 100644 runners/characters/charrunner_config.cpp create mode 100644 runners/characters/charrunner_config.h create mode 100644 runners/characters/charrunner_config.ui create mode 100644 runners/characters/config_keys.h create mode 100644 runners/characters/plasma-runner-character.json create mode 100644 runners/converter/CMakeLists.txt create mode 100755 runners/converter/Messages.sh create mode 100644 runners/converter/autotests/CMakeLists.txt create mode 100644 runners/converter/autotests/converterrunnertest.cpp create mode 100644 runners/converter/converterrunner.cpp create mode 100644 runners/converter/converterrunner.h create mode 100644 runners/converter/plasma-runner-converter.json create mode 100644 runners/datetime/CMakeLists.txt create mode 100644 runners/datetime/Messages.sh create mode 100644 runners/datetime/autotests/CMakeLists.txt create mode 100644 runners/datetime/autotests/datetimerunnertest.cpp create mode 100644 runners/datetime/datetimerunner.cpp create mode 100644 runners/datetime/datetimerunner.h create mode 100644 runners/datetime/plasma-runner-datetime.json create mode 100644 runners/dictionary/CMakeLists.txt create mode 100755 runners/dictionary/Messages.sh create mode 100644 runners/dictionary/dictionaryrunner.cpp create mode 100644 runners/dictionary/dictionaryrunner.h create mode 100644 runners/dictionary/dictionaryrunner_config.cpp create mode 100644 runners/dictionary/dictionaryrunner_config.h create mode 100644 runners/dictionary/plasma-runner-dictionary.json create mode 100644 runners/katesessions/CMakeLists.txt create mode 100755 runners/katesessions/Messages.sh create mode 100644 runners/katesessions/README create mode 100644 runners/katesessions/katesessions.cpp create mode 100644 runners/katesessions/katesessions.h create mode 100644 runners/katesessions/plasma-runner-katesessions.json create mode 100644 runners/konsoleprofiles/CMakeLists.txt create mode 100755 runners/konsoleprofiles/Messages.sh create mode 100644 runners/konsoleprofiles/konsoleprofiles.cpp create mode 100644 runners/konsoleprofiles/konsoleprofiles.h create mode 100644 runners/konsoleprofiles/plasma-runner-konsoleprofiles.json create mode 100644 runners/spellchecker/CMakeLists.txt create mode 100755 runners/spellchecker/Messages.sh create mode 100644 runners/spellchecker/autotests/CMakeLists.txt create mode 100644 runners/spellchecker/autotests/spellcheckrunnertest.cpp create mode 100644 runners/spellchecker/plasma-runner-spellchecker.json create mode 100644 runners/spellchecker/spellcheck.cpp create mode 100644 runners/spellchecker/spellcheck.h create mode 100644 runners/spellchecker/spellcheck_config.cpp create mode 100644 runners/spellchecker/spellcheck_config.h create mode 100644 runners/spellchecker/spellcheck_config.ui create mode 100644 templates/.clang-format create mode 100644 templates/CMakeLists.txt create mode 100644 templates/plasmapotdprovider/CMakeLists.txt create mode 100644 templates/plasmapotdprovider/README create mode 100644 templates/plasmapotdprovider/plasmapotdprovider.kdevtemplate create mode 100644 templates/plasmapotdprovider/src/%{APPNAMELC}.cpp create mode 100644 templates/plasmapotdprovider/src/%{APPNAMELC}.h create mode 100644 templates/plasmapotdprovider/src/%{APPNAMELC}.json create mode 100644 templates/plasmapotdprovider/src/CMakeLists.txt create mode 100644 wallpapers/CMakeLists.txt create mode 100644 wallpapers/haenau/contents/ui/BackgroundElement.qml create mode 100644 wallpapers/haenau/contents/ui/BottomBackgroundElement.qml create mode 100644 wallpapers/haenau/contents/ui/RightBackgroundElement.qml create mode 100644 wallpapers/haenau/contents/ui/main.qml create mode 100644 wallpapers/haenau/contents/ui/wallpaper.svgz create mode 100644 wallpapers/haenau/metadata.json create mode 100644 wallpapers/hunyango/contents/ui/ColorProvider.qml create mode 100644 wallpapers/hunyango/contents/ui/main.qml create mode 100644 wallpapers/hunyango/contents/ui/qmldir create mode 100644 wallpapers/hunyango/contents/ui/wallpaper.svgz create mode 100644 wallpapers/hunyango/metadata.json create mode 100644 wallpapers/potd/CMakeLists.txt create mode 100755 wallpapers/potd/Messages.sh create mode 100644 wallpapers/potd/package/contents/config/main.xml create mode 100644 wallpapers/potd/package/contents/ui/ActionContextMenu.qml create mode 100644 wallpapers/potd/package/contents/ui/WallpaperDelegate.qml create mode 100644 wallpapers/potd/package/contents/ui/WallpaperPreview.qml create mode 100644 wallpapers/potd/package/contents/ui/config.qml create mode 100644 wallpapers/potd/package/contents/ui/main.qml create mode 100644 wallpapers/potd/package/metadata.json create mode 100644 wallpapers/potd/plugins/CMakeLists.txt create mode 100644 wallpapers/potd/plugins/HowToAddProvider.txt create mode 100644 wallpapers/potd/plugins/PlasmaPotdProvider.cmake.in create mode 100644 wallpapers/potd/plugins/cachedprovider.cpp create mode 100644 wallpapers/potd/plugins/cachedprovider.h create mode 100644 wallpapers/potd/plugins/plasma_potdprovider.json create mode 100644 wallpapers/potd/plugins/potdbackend.cpp create mode 100644 wallpapers/potd/plugins/potdbackend.h create mode 100644 wallpapers/potd/plugins/potdengine.cpp create mode 100644 wallpapers/potd/plugins/potdengine.h create mode 100644 wallpapers/potd/plugins/potdplugin.cpp create mode 100644 wallpapers/potd/plugins/potdprovider.cpp create mode 100644 wallpapers/potd/plugins/potdprovider.h create mode 100644 wallpapers/potd/plugins/potdprovidermodel.cpp create mode 100644 wallpapers/potd/plugins/potdprovidermodel.h create mode 100644 wallpapers/potd/plugins/providers/CMakeLists.txt create mode 100644 wallpapers/potd/plugins/providers/PoTD-list.txt create mode 100644 wallpapers/potd/plugins/providers/apodprovider.cpp create mode 100644 wallpapers/potd/plugins/providers/apodprovider.h create mode 100644 wallpapers/potd/plugins/providers/apodprovider.json create mode 100644 wallpapers/potd/plugins/providers/bingprovider.cpp create mode 100644 wallpapers/potd/plugins/providers/bingprovider.h create mode 100644 wallpapers/potd/plugins/providers/bingprovider.json create mode 100644 wallpapers/potd/plugins/providers/epodprovider.cpp create mode 100644 wallpapers/potd/plugins/providers/epodprovider.h create mode 100644 wallpapers/potd/plugins/providers/epodprovider.json create mode 100644 wallpapers/potd/plugins/providers/flickrprovider.cpp create mode 100644 wallpapers/potd/plugins/providers/flickrprovider.h create mode 100644 wallpapers/potd/plugins/providers/flickrprovider.json create mode 100644 wallpapers/potd/plugins/providers/noaaprovider.cpp create mode 100644 wallpapers/potd/plugins/providers/noaaprovider.h create mode 100644 wallpapers/potd/plugins/providers/noaaprovider.json create mode 100644 wallpapers/potd/plugins/providers/pixabayprovider.conf create mode 100644 wallpapers/potd/plugins/providers/simonstalenhagprovider.cpp create mode 100644 wallpapers/potd/plugins/providers/simonstalenhagprovider.h create mode 100644 wallpapers/potd/plugins/providers/simonstalenhagprovider.json create mode 100644 wallpapers/potd/plugins/providers/wcpotdprovider.cpp create mode 100644 wallpapers/potd/plugins/providers/wcpotdprovider.h create mode 100644 wallpapers/potd/plugins/providers/wcpotdprovider.json diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 00000000..764f8040 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: None +# SPDX-License-Identifier: CC0-1.0 + +# clang-tidy: Force braces around statements +d674bee1947d75d598e194a9244f3b8d7e127717 +# clang-format +039f7d90421fa42ae11c62b8b2a19eb0aa784962 diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..eab23c1a --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +# SPDX-FileCopyrightText: None +# SPDX-License-Identifier: CC0-1.0 + +# Ignore the following files +*~ +*.[oa] +*.diff +*.kate-swp +*.kdev4 +.kdev_include_paths +*.kdevelop.pcs +*.moc +*.moc.cpp +*.orig +*.user +.*.swp +.swp.* +Doxyfile +Makefile +avail +random_seed +/build*/ +CMakeLists.txt.user* +*.unc-backup* +.clang-format +/build*/ +cmake-build-debug* +.idea +/compile_commands.json +.clangd +.cache diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 00000000..f6e2f69c --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: None +# SPDX-License-Identifier: CC0-1.0 + +include: + - project: sysadmin/ci-utilities + file: + - /gitlab-templates/linux-qt6.yml + - /gitlab-templates/freebsd-qt6.yml diff --git a/.kde-ci.yml b/.kde-ci.yml new file mode 100644 index 00000000..f05a7eb0 --- /dev/null +++ b/.kde-ci.yml @@ -0,0 +1,47 @@ +# SPDX-FileCopyrightText: None +# SPDX-License-Identifier: CC0-1.0 + +Dependencies: +- 'on': ['@all'] + 'require': + 'frameworks/attica': '@latest-kf6' + 'frameworks/extra-cmake-modules': '@latest-kf6' + 'frameworks/kauth': '@latest-kf6' + 'frameworks/kcmutils': '@latest-kf6' + 'frameworks/kcodecs': '@latest-kf6' + 'frameworks/kcompletion': '@latest-kf6' + 'frameworks/kconfig': '@latest-kf6' + 'frameworks/kconfigwidgets': '@latest-kf6' + 'frameworks/kcoreaddons': '@latest-kf6' + 'frameworks/kglobalaccel': '@latest-kf6' + 'frameworks/kholidays': '@latest-kf6' + 'frameworks/ki18n': '@latest-kf6' + 'frameworks/kio': '@latest-kf6' + 'frameworks/kjobwidgets': '@latest-kf6' + 'frameworks/knewstuff': '@latest-kf6' + 'frameworks/knotifications': '@latest-kf6' + 'frameworks/kpackage': '@latest-kf6' + 'frameworks/krunner': '@latest-kf6' + 'frameworks/kservice': '@latest-kf6' + 'frameworks/kwidgetsaddons': '@latest-kf6' + 'frameworks/kxmlgui': '@latest-kf6' + 'frameworks/solid': '@latest-kf6' + 'frameworks/sonnet': '@latest-kf6' + 'frameworks/kunitconversion': '@latest-kf6' + 'libraries/kirigami-addons': '@latest-kf6' + 'plasma/libplasma': '@same' + 'plasma/plasma-workspace': '@same' + 'plasma/plasma5support': '@same' +- 'on': ['Linux'] + 'require': + 'sdk/selenium-webdriver-at-spi': '@latest-kf6' + +RuntimeDependencies: +- 'on': ['Linux'] + 'require': + 'plasma/plasma-desktop': '@same' + 'plasma/plasma-nano': '@same' # For appium tests + +Options: + require-passing-tests-on: [ 'Linux', 'FreeBSD'] + cppcheck-ignore-files: ['templates/'] diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..f47df2dc --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,151 @@ +cmake_minimum_required(VERSION 3.16) + +project(kdeplasma-addons) +set(PROJECT_VERSION "6.2.4") +set(PROJECT_VERSION_MAJOR 6) + +################# Disallow in-source build ################# +if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}") + message(FATAL_ERROR "plasma requires an out of source build. Please create a separate build directory and run 'cmake path_to_plasma [options]' there.") +endif() + +set(PROJECT_DEP_VERSION "6.2.4") +set(QT_MIN_VERSION "6.7.0") +set(KF6_MIN_VERSION "6.5.0") +set(KDE_COMPILERSETTINGS_LEVEL "5.82") + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +include(FeatureSummary) + +find_package(ECM ${KF6_MIN_VERSION} REQUIRED NO_MODULE) +set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) + +include(KDEInstallDirs) +include(KDECMakeSettings) +include(KDECompilerSettings NO_POLICY_SCOPE) +include(ECMQtDeclareLoggingCategory) +include(ECMGenerateExportHeader) +include(ECMInstallIcons) +include(KDEPackageAppTemplates) +include(GenerateExportHeader) +include(CMakePackageConfigHelpers) +include(KDEClangFormat) +include(KDEGitCommitHooks) +include(ECMDeprecationSettings) +include(ECMQmlModule) + +find_package(Qt6 ${QT_MIN_VERSION} CONFIG REQUIRED + Core + Gui + DBus + Network + Quick + Qml + Widgets + Test + Core5Compat +) + +find_package(KF6 ${KF6_MIN_VERSION} REQUIRED COMPONENTS + Config + CoreAddons + DBusAddons + Declarative + GlobalAccel + Holidays + I18n + Auth + KIO + KCMUtils + Notifications + Runner + Service + Sonnet + UnitConversion + XmlGui + NewStuff + JobWidgets +) + +find_package(Plasma5Support ${PROJECT_DEP_VERSION} REQUIRED) +find_package(Plasma ${PROJECT_DEP_VERSION} REQUIRED) +find_package(PlasmaQuick ${PROJECT_DEP_VERSION} REQUIRED) + +find_package(KF6Purpose CONFIG QUIET) +set_package_properties(KF6Purpose PROPERTIES + DESCRIPTION "Framework for cross-application services and actions" + PURPOSE "Needed for QuickShare applet" + URL "https://commits.kde.org/purpose" + TYPE RUNTIME +) + +find_package(KirigamiAddons CONFIG) +set_package_properties(KirigamiAddons PROPERTIES + DESCRIPTION "Extra controls for Kirigami applications" + PURPOSE "Required at runtime for the User List widget" + TYPE RUNTIME +) + +find_package(KItemModels CONFIG) +set_package_properties(KItemModels PROPERTIES + DESCRIPTION "Set of item models extending the Qt model-view framework" + PURPOSE "Required at runtime for the Dictionary, Kate Profiles, and Konsole Profiles applets" + TYPE RUNTIME +) + +find_package(ICU 66.1 COMPONENTS uc i18n) +set_package_properties(ICU + PROPERTIES DESCRIPTION "Unicode and Globalization support for software applications" + TYPE OPTIONAL + PURPOSE "Provides alternate calendar systems that are not available in QCalendar" + ) +if(ICU_FOUND) + set(HAVE_ICU TRUE) +endif() + +find_package(Qt6Quick3D CONFIG) +set_package_properties(Qt6Quick3D PROPERTIES + DESCRIPTION "A high-level API for creating 3D content and 3D user interfaces based on Qt Quick" + PURPOSE "Required for desktop cube" + TYPE RUNTIME +) + +add_definitions( + -DQT_DEPRECATED_WARNINGS + -DQT_NO_URL_CAST_FROM_STRING +) + +ecm_set_disabled_deprecation_versions( + QT 6.5.0 +) + +add_subdirectory(dict) +add_subdirectory(kdeds) +add_subdirectory(profiles) +add_subdirectory(applets) +add_subdirectory(runners) + +add_subdirectory(wallpapers) + +add_subdirectory(kwin) + +add_subdirectory(plasmacalendarplugins) + +add_subdirectory(templates) +add_subdirectory(appiumtests) + +# add clang-format target for all our real source files +file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES *.cpp *.h) +kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES}) +kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT) + +ki18n_install(po) +ecm_qt_install_logging_categories(EXPORT KDEPLASMAADDONS + FILE kdeplasma-addons.categories + DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR} +) + + +feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/LICENSES/BSD-3-Clause.txt b/LICENSES/BSD-3-Clause.txt new file mode 100644 index 00000000..0741db78 --- /dev/null +++ b/LICENSES/BSD-3-Clause.txt @@ -0,0 +1,26 @@ +Copyright (c) . All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/LICENSES/CC0-1.0.txt b/LICENSES/CC0-1.0.txt new file mode 100644 index 00000000..0e259d42 --- /dev/null +++ b/LICENSES/CC0-1.0.txt @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/LICENSES/GPL-2.0-only.txt b/LICENSES/GPL-2.0-only.txt new file mode 100644 index 00000000..0f3d6411 --- /dev/null +++ b/LICENSES/GPL-2.0-only.txt @@ -0,0 +1,319 @@ +GNU GENERAL PUBLIC LICENSE + +Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. + +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +Preamble + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public License is intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. This General Public License applies to +most of the Free Software Foundation's software and to any other program whose +authors commit to using it. (Some other Free Software Foundation software +is covered by the GNU Lesser General Public License instead.) You can apply +it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our +General Public Licenses are designed to make sure that you have the freedom +to distribute copies of free software (and charge for this service if you +wish), that you receive source code or can get it if you want it, that you +can change the software or use pieces of it in new free programs; and that +you know you can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to +deny you these rights or to ask you to surrender the rights. These restrictions +translate to certain responsibilities for you if you distribute copies of +the software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or +for a fee, you must give the recipients all the rights that you have. You +must make sure that they, too, receive or can get the source code. And you +must show them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) +offer you this license which gives you legal permission to copy, distribute +and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that +everyone understands that there is no warranty for this free software. If +the software is modified by someone else and passed on, we want its recipients +to know that what they have is not the original, so that any problems introduced +by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We +wish to avoid the danger that redistributors of a free program will individually +obtain patent licenses, in effect making the program proprietary. To prevent +this, we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification +follow. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License applies to any program or other work which contains a notice +placed by the copyright holder saying it may be distributed under the terms +of this General Public License. The "Program", below, refers to any such program +or work, and a "work based on the Program" means either the Program or any +derivative work under copyright law: that is to say, a work containing the +Program or a portion of it, either verbatim or with modifications and/or translated +into another language. (Hereinafter, translation is included without limitation +in the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not covered +by this License; they are outside its scope. The act of running the Program +is not restricted, and the output from the Program is covered only if its +contents constitute a work based on the Program (independent of having been +made by running the Program). Whether that is true depends on what the Program +does. + +1. You may copy and distribute verbatim copies of the Program's source code +as you receive it, in any medium, provided that you conspicuously and appropriately +publish on each copy an appropriate copyright notice and disclaimer of warranty; +keep intact all the notices that refer to this License and to the absence +of any warranty; and give any other recipients of the Program a copy of this +License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you +may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, +thus forming a work based on the Program, and copy and distribute such modifications +or work under the terms of Section 1 above, provided that you also meet all +of these conditions: + +a) You must cause the modified files to carry prominent notices stating that +you changed the files and the date of any change. + +b) You must cause any work that you distribute or publish, that in whole or +in part contains or is derived from the Program or any part thereof, to be +licensed as a whole at no charge to all third parties under the terms of this +License. + +c) If the modified program normally reads commands interactively when run, +you must cause it, when started running for such interactive use in the most +ordinary way, to print or display an announcement including an appropriate +copyright notice and a notice that there is no warranty (or else, saying that +you provide a warranty) and that users may redistribute the program under +these conditions, and telling the user how to view a copy of this License. +(Exception: if the Program itself is interactive but does not normally print +such an announcement, your work based on the Program is not required to print +an announcement.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Program, and can be reasonably +considered independent and separate works in themselves, then this License, +and its terms, do not apply to those sections when you distribute them as +separate works. But when you distribute the same sections as part of a whole +which is a work based on the Program, the distribution of the whole must be +on the terms of this License, whose permissions for other licensees extend +to the entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest your +rights to work written entirely by you; rather, the intent is to exercise +the right to control the distribution of derivative or collective works based +on the Program. + +In addition, mere aggregation of another work not based on the Program with +the Program (or with a work based on the Program) on a volume of a storage +or distribution medium does not bring the other work under the scope of this +License. + +3. You may copy and distribute the Program (or a work based on it, under Section +2) in object code or executable form under the terms of Sections 1 and 2 above +provided that you also do one of the following: + +a) Accompany it with the complete corresponding machine-readable source code, +which must be distributed under the terms of Sections 1 and 2 above on a medium +customarily used for software interchange; or, + +b) Accompany it with a written offer, valid for at least three years, to give +any third party, for a charge no more than your cost of physically performing +source distribution, a complete machine-readable copy of the corresponding +source code, to be distributed under the terms of Sections 1 and 2 above on +a medium customarily used for software interchange; or, + +c) Accompany it with the information you received as to the offer to distribute +corresponding source code. (This alternative is allowed only for noncommercial +distribution and only if you received the program in object code or executable +form with such an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for making +modifications to it. For an executable work, complete source code means all +the source code for all modules it contains, plus any associated interface +definition files, plus the scripts used to control compilation and installation +of the executable. However, as a special exception, the source code distributed +need not include anything that is normally distributed (in either source or +binary form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component itself +accompanies the executable. + +If distribution of executable or object code is made by offering access to +copy from a designated place, then offering equivalent access to copy the +source code from the same place counts as distribution of the source code, +even though third parties are not compelled to copy the source along with +the object code. + +4. You may not copy, modify, sublicense, or distribute the Program except +as expressly provided under this License. Any attempt otherwise to copy, modify, +sublicense or distribute the Program is void, and will automatically terminate +your rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses terminated +so long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed +it. However, nothing else grants you permission to modify or distribute the +Program or its derivative works. These actions are prohibited by law if you +do not accept this License. Therefore, by modifying or distributing the Program +(or any work based on the Program), you indicate your acceptance of this License +to do so, and all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the Program), +the recipient automatically receives a license from the original licensor +to copy, distribute or modify the Program subject to these terms and conditions. +You may not impose any further restrictions on the recipients' exercise of +the rights granted herein. You are not responsible for enforcing compliance +by third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent infringement +or for any other reason (not limited to patent issues), conditions are imposed +on you (whether by court order, agreement or otherwise) that contradict the +conditions of this License, they do not excuse you from the conditions of +this License. If you cannot distribute so as to satisfy simultaneously your +obligations under this License and any other pertinent obligations, then as +a consequence you may not distribute the Program at all. For example, if a +patent license would not permit royalty-free redistribution of the Program +by all those who receive copies directly or indirectly through you, then the +only way you could satisfy both it and this License would be to refrain entirely +from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply and +the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents +or other property right claims or to contest validity of any such claims; +this section has the sole purpose of protecting the integrity of the free +software distribution system, which is implemented by public license practices. +Many people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose +that choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain +countries either by patents or by copyrighted interfaces, the original copyright +holder who places the Program under this License may add an explicit geographical +distribution limitation excluding those countries, so that distribution is +permitted only in or among countries not thus excluded. In such case, this +License incorporates the limitation as if written in the body of this License. + +9. The Free Software Foundation may publish revised and/or new versions of +the General Public License from time to time. Such new versions will be similar +in spirit to the present version, but may differ in detail to address new +problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies +a version number of this License which applies to it and "any later version", +you have the option of following the terms and conditions either of that version +or of any later version published by the Free Software Foundation. If the +Program does not specify a version number of this License, you may choose +any version ever published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs +whose distribution conditions are different, write to the author to ask for +permission. For software which is copyrighted by the Free Software Foundation, +write to the Free Software Foundation; we sometimes make exceptions for this. +Our decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing and reuse +of software generally. + + NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR +THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE +STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM +"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE +OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE +OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA +OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES +OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH +HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible +use to the public, the best way to achieve this is to make it free software +which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach +them to the start of each source file to most effectively convey the exclusion +of warranty; and each file should have at least the "copyright" line and a +pointer to where the full notice is found. + + + +Copyright (C)< yyyy> + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 51 Franklin +Street, Fifth Floor, Boston, MA 02110-1301, USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this when +it starts in an interactive mode: + +Gnomovision version 69, Copyright (C) year name of author Gnomovision comes +with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, +and you are welcome to redistribute it under certain conditions; type `show +c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may be +called something other than `show w' and `show c'; they could even be mouse-clicks +or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your school, +if any, to sign a "copyright disclaimer" for the program, if necessary. Here +is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' +(which makes passes at compilers) written by James Hacker. + +, 1 April 1989 Ty Coon, President of Vice This General +Public License does not permit incorporating your program into proprietary +programs. If your program is a subroutine library, you may consider it more +useful to permit linking proprietary applications with the library. If this +is what you want to do, use the GNU Lesser General Public License instead +of this License. diff --git a/LICENSES/GPL-2.0-or-later.txt b/LICENSES/GPL-2.0-or-later.txt new file mode 100644 index 00000000..1d80ac36 --- /dev/null +++ b/LICENSES/GPL-2.0-or-later.txt @@ -0,0 +1,319 @@ +GNU GENERAL PUBLIC LICENSE + +Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. + +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +Preamble + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public License is intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. This General Public License applies to +most of the Free Software Foundation's software and to any other program whose +authors commit to using it. (Some other Free Software Foundation software +is covered by the GNU Lesser General Public License instead.) You can apply +it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our +General Public Licenses are designed to make sure that you have the freedom +to distribute copies of free software (and charge for this service if you +wish), that you receive source code or can get it if you want it, that you +can change the software or use pieces of it in new free programs; and that +you know you can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to +deny you these rights or to ask you to surrender the rights. These restrictions +translate to certain responsibilities for you if you distribute copies of +the software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or +for a fee, you must give the recipients all the rights that you have. You +must make sure that they, too, receive or can get the source code. And you +must show them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) +offer you this license which gives you legal permission to copy, distribute +and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that +everyone understands that there is no warranty for this free software. If +the software is modified by someone else and passed on, we want its recipients +to know that what they have is not the original, so that any problems introduced +by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We +wish to avoid the danger that redistributors of a free program will individually +obtain patent licenses, in effect making the program proprietary. To prevent +this, we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification +follow. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License applies to any program or other work which contains a notice +placed by the copyright holder saying it may be distributed under the terms +of this General Public License. The "Program", below, refers to any such program +or work, and a "work based on the Program" means either the Program or any +derivative work under copyright law: that is to say, a work containing the +Program or a portion of it, either verbatim or with modifications and/or translated +into another language. (Hereinafter, translation is included without limitation +in the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not covered +by this License; they are outside its scope. The act of running the Program +is not restricted, and the output from the Program is covered only if its +contents constitute a work based on the Program (independent of having been +made by running the Program). Whether that is true depends on what the Program +does. + +1. You may copy and distribute verbatim copies of the Program's source code +as you receive it, in any medium, provided that you conspicuously and appropriately +publish on each copy an appropriate copyright notice and disclaimer of warranty; +keep intact all the notices that refer to this License and to the absence +of any warranty; and give any other recipients of the Program a copy of this +License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you +may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, +thus forming a work based on the Program, and copy and distribute such modifications +or work under the terms of Section 1 above, provided that you also meet all +of these conditions: + +a) You must cause the modified files to carry prominent notices stating that +you changed the files and the date of any change. + +b) You must cause any work that you distribute or publish, that in whole or +in part contains or is derived from the Program or any part thereof, to be +licensed as a whole at no charge to all third parties under the terms of this +License. + +c) If the modified program normally reads commands interactively when run, +you must cause it, when started running for such interactive use in the most +ordinary way, to print or display an announcement including an appropriate +copyright notice and a notice that there is no warranty (or else, saying that +you provide a warranty) and that users may redistribute the program under +these conditions, and telling the user how to view a copy of this License. +(Exception: if the Program itself is interactive but does not normally print +such an announcement, your work based on the Program is not required to print +an announcement.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Program, and can be reasonably +considered independent and separate works in themselves, then this License, +and its terms, do not apply to those sections when you distribute them as +separate works. But when you distribute the same sections as part of a whole +which is a work based on the Program, the distribution of the whole must be +on the terms of this License, whose permissions for other licensees extend +to the entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest your +rights to work written entirely by you; rather, the intent is to exercise +the right to control the distribution of derivative or collective works based +on the Program. + +In addition, mere aggregation of another work not based on the Program with +the Program (or with a work based on the Program) on a volume of a storage +or distribution medium does not bring the other work under the scope of this +License. + +3. You may copy and distribute the Program (or a work based on it, under Section +2) in object code or executable form under the terms of Sections 1 and 2 above +provided that you also do one of the following: + +a) Accompany it with the complete corresponding machine-readable source code, +which must be distributed under the terms of Sections 1 and 2 above on a medium +customarily used for software interchange; or, + +b) Accompany it with a written offer, valid for at least three years, to give +any third party, for a charge no more than your cost of physically performing +source distribution, a complete machine-readable copy of the corresponding +source code, to be distributed under the terms of Sections 1 and 2 above on +a medium customarily used for software interchange; or, + +c) Accompany it with the information you received as to the offer to distribute +corresponding source code. (This alternative is allowed only for noncommercial +distribution and only if you received the program in object code or executable +form with such an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for making +modifications to it. For an executable work, complete source code means all +the source code for all modules it contains, plus any associated interface +definition files, plus the scripts used to control compilation and installation +of the executable. However, as a special exception, the source code distributed +need not include anything that is normally distributed (in either source or +binary form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component itself +accompanies the executable. + +If distribution of executable or object code is made by offering access to +copy from a designated place, then offering equivalent access to copy the +source code from the same place counts as distribution of the source code, +even though third parties are not compelled to copy the source along with +the object code. + +4. You may not copy, modify, sublicense, or distribute the Program except +as expressly provided under this License. Any attempt otherwise to copy, modify, +sublicense or distribute the Program is void, and will automatically terminate +your rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses terminated +so long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed +it. However, nothing else grants you permission to modify or distribute the +Program or its derivative works. These actions are prohibited by law if you +do not accept this License. Therefore, by modifying or distributing the Program +(or any work based on the Program), you indicate your acceptance of this License +to do so, and all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the Program), +the recipient automatically receives a license from the original licensor +to copy, distribute or modify the Program subject to these terms and conditions. +You may not impose any further restrictions on the recipients' exercise of +the rights granted herein. You are not responsible for enforcing compliance +by third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent infringement +or for any other reason (not limited to patent issues), conditions are imposed +on you (whether by court order, agreement or otherwise) that contradict the +conditions of this License, they do not excuse you from the conditions of +this License. If you cannot distribute so as to satisfy simultaneously your +obligations under this License and any other pertinent obligations, then as +a consequence you may not distribute the Program at all. For example, if a +patent license would not permit royalty-free redistribution of the Program +by all those who receive copies directly or indirectly through you, then the +only way you could satisfy both it and this License would be to refrain entirely +from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply and +the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents +or other property right claims or to contest validity of any such claims; +this section has the sole purpose of protecting the integrity of the free +software distribution system, which is implemented by public license practices. +Many people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose +that choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain +countries either by patents or by copyrighted interfaces, the original copyright +holder who places the Program under this License may add an explicit geographical +distribution limitation excluding those countries, so that distribution is +permitted only in or among countries not thus excluded. In such case, this +License incorporates the limitation as if written in the body of this License. + +9. The Free Software Foundation may publish revised and/or new versions of +the General Public License from time to time. Such new versions will be similar +in spirit to the present version, but may differ in detail to address new +problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies +a version number of this License which applies to it and "any later version", +you have the option of following the terms and conditions either of that version +or of any later version published by the Free Software Foundation. If the +Program does not specify a version number of this License, you may choose +any version ever published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs +whose distribution conditions are different, write to the author to ask for +permission. For software which is copyrighted by the Free Software Foundation, +write to the Free Software Foundation; we sometimes make exceptions for this. +Our decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing and reuse +of software generally. + + NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR +THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE +STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM +"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE +OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE +OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA +OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES +OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH +HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible +use to the public, the best way to achieve this is to make it free software +which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach +them to the start of each source file to most effectively convey the exclusion +of warranty; and each file should have at least the "copyright" line and a +pointer to where the full notice is found. + + + +Copyright (C) + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 51 Franklin +Street, Fifth Floor, Boston, MA 02110-1301, USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this when +it starts in an interactive mode: + +Gnomovision version 69, Copyright (C) year name of author Gnomovision comes +with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, +and you are welcome to redistribute it under certain conditions; type `show +c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may be +called something other than `show w' and `show c'; they could even be mouse-clicks +or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your school, +if any, to sign a "copyright disclaimer" for the program, if necessary. Here +is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' +(which makes passes at compilers) written by James Hacker. + +, 1 April 1989 Ty Coon, President of Vice This General +Public License does not permit incorporating your program into proprietary +programs. If your program is a subroutine library, you may consider it more +useful to permit linking proprietary applications with the library. If this +is what you want to do, use the GNU Lesser General Public License instead +of this License. diff --git a/LICENSES/GPL-3.0-only.txt b/LICENSES/GPL-3.0-only.txt new file mode 100644 index 00000000..e142a525 --- /dev/null +++ b/LICENSES/GPL-3.0-only.txt @@ -0,0 +1,625 @@ +GNU GENERAL PUBLIC LICENSE + +Version 3, 29 June 2007 + +Copyright © 2007 Free Software Foundation, Inc. + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +Preamble + +The GNU General Public License is a free, copyleft license for software and +other kinds of works. + +The licenses for most software and other practical works are designed to take +away your freedom to share and change the works. By contrast, the GNU General +Public License is intended to guarantee your freedom to share and change all +versions of a program--to make sure it remains free software for all its users. +We, the Free Software Foundation, use the GNU General Public License for most +of our software; it applies also to any other work released this way by its +authors. You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our +General Public Licenses are designed to make sure that you have the freedom +to distribute copies of free software (and charge for them if you wish), that +you receive source code or can get it if you want it, that you can change +the software or use pieces of it in new free programs, and that you know you +can do these things. + +To protect your rights, we need to prevent others from denying you these rights +or asking you to surrender the rights. Therefore, you have certain responsibilities +if you distribute copies of the software, or if you modify it: responsibilities +to respect the freedom of others. + +For example, if you distribute copies of such a program, whether gratis or +for a fee, you must pass on to the recipients the same freedoms that you received. +You must make sure that they, too, receive or can get the source code. And +you must show them these terms so they know their rights. + +Developers that use the GNU GPL protect your rights with two steps: (1) assert +copyright on the software, and (2) offer you this License giving you legal +permission to copy, distribute and/or modify it. + +For the developers' and authors' protection, the GPL clearly explains that +there is no warranty for this free software. For both users' and authors' +sake, the GPL requires that modified versions be marked as changed, so that +their problems will not be attributed erroneously to authors of previous versions. + +Some devices are designed to deny users access to install or run modified +versions of the software inside them, although the manufacturer can do so. +This is fundamentally incompatible with the aim of protecting users' freedom +to change the software. The systematic pattern of such abuse occurs in the +area of products for individuals to use, which is precisely where it is most +unacceptable. Therefore, we have designed this version of the GPL to prohibit +the practice for those products. If such problems arise substantially in other +domains, we stand ready to extend this provision to those domains in future +versions of the GPL, as needed to protect the freedom of users. + +Finally, every program is threatened constantly by software patents. States +should not allow patents to restrict development and use of software on general-purpose +computers, but in those that do, we wish to avoid the special danger that +patents applied to a free program could make it effectively proprietary. To +prevent this, the GPL assures that patents cannot be used to render the program +non-free. + +The precise terms and conditions for copying, distribution and modification +follow. + +TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + +"Copyright" also means copyright-like laws that apply to other kinds of works, +such as semiconductor masks. + +"The Program" refers to any copyrightable work licensed under this License. +Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals +or organizations. + +To "modify" a work means to copy from or adapt all or part of the work in +a fashion requiring copyright permission, other than the making of an exact +copy. The resulting work is called a "modified version" of the earlier work +or a work "based on" the earlier work. + +A "covered work" means either the unmodified Program or a work based on the +Program. + +To "propagate" a work means to do anything with it that, without permission, +would make you directly or secondarily liable for infringement under applicable +copyright law, except executing it on a computer or modifying a private copy. +Propagation includes copying, distribution (with or without modification), +making available to the public, and in some countries other activities as +well. + +To "convey" a work means any kind of propagation that enables other parties +to make or receive copies. Mere interaction with a user through a computer +network, with no transfer of a copy, is not conveying. + +An interactive user interface displays "Appropriate Legal Notices" to the +extent that it includes a convenient and prominently visible feature that +(1) displays an appropriate copyright notice, and (2) tells the user that +there is no warranty for the work (except to the extent that warranties are +provided), that licensees may convey the work under this License, and how +to view a copy of this License. If the interface presents a list of user commands +or options, such as a menu, a prominent item in the list meets this criterion. + + 1. Source Code. + +The "source code" for a work means the preferred form of the work for making +modifications to it. "Object code" means any non-source form of a work. + +A "Standard Interface" means an interface that either is an official standard +defined by a recognized standards body, or, in the case of interfaces specified +for a particular programming language, one that is widely used among developers +working in that language. + +The "System Libraries" of an executable work include anything, other than +the work as a whole, that (a) is included in the normal form of packaging +a Major Component, but which is not part of that Major Component, and (b) +serves only to enable use of the work with that Major Component, or to implement +a Standard Interface for which an implementation is available to the public +in source code form. A "Major Component", in this context, means a major essential +component (kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to produce +the work, or an object code interpreter used to run it. + +The "Corresponding Source" for a work in object code form means all the source +code needed to generate, install, and (for an executable work) run the object +code and to modify the work, including scripts to control those activities. +However, it does not include the work's System Libraries, or general-purpose +tools or generally available free programs which are used unmodified in performing +those activities but which are not part of the work. For example, Corresponding +Source includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically linked +subprograms that the work is specifically designed to require, such as by +intimate data communication or control flow between those subprograms and +other parts of the work. + +The Corresponding Source need not include anything that users can regenerate +automatically from other parts of the Corresponding Source. + + The Corresponding Source for a work in source code form is that same work. + + 2. Basic Permissions. + +All rights granted under this License are granted for the term of copyright +on the Program, and are irrevocable provided the stated conditions are met. +This License explicitly affirms your unlimited permission to run the unmodified +Program. The output from running a covered work is covered by this License +only if the output, given its content, constitutes a covered work. This License +acknowledges your rights of fair use or other equivalent, as provided by copyright +law. + +You may make, run and propagate covered works that you do not convey, without +conditions so long as your license otherwise remains in force. You may convey +covered works to others for the sole purpose of having them make modifications +exclusively for you, or provide you with facilities for running those works, +provided that you comply with the terms of this License in conveying all material +for which you do not control copyright. Those thus making or running the covered +works for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of your copyrighted +material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the conditions +stated below. Sublicensing is not allowed; section 10 makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + +No covered work shall be deemed part of an effective technological measure +under any applicable law fulfilling obligations under article 11 of the WIPO +copyright treaty adopted on 20 December 1996, or similar laws prohibiting +or restricting circumvention of such measures. + +When you convey a covered work, you waive any legal power to forbid circumvention +of technological measures to the extent such circumvention is effected by +exercising rights under this License with respect to the covered work, and +you disclaim any intention to limit operation or modification of the work +as a means of enforcing, against the work's users, your or third parties' +legal rights to forbid circumvention of technological measures. + + 4. Conveying Verbatim Copies. + +You may convey verbatim copies of the Program's source code as you receive +it, in any medium, provided that you conspicuously and appropriately publish +on each copy an appropriate copyright notice; keep intact all notices stating +that this License and any non-permissive terms added in accord with section +7 apply to the code; keep intact all notices of the absence of any warranty; +and give all recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, and you +may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + +You may convey a work based on the Program, or the modifications to produce +it from the Program, in the form of source code under the terms of section +4, provided that you also meet all of these conditions: + +a) The work must carry prominent notices stating that you modified it, and +giving a relevant date. + +b) The work must carry prominent notices stating that it is released under +this License and any conditions added under section 7. This requirement modifies +the requirement in section 4 to "keep intact all notices". + +c) You must license the entire work, as a whole, under this License to anyone +who comes into possession of a copy. This License will therefore apply, along +with any applicable section 7 additional terms, to the whole of the work, +and all its parts, regardless of how they are packaged. This License gives +no permission to license the work in any other way, but it does not invalidate +such permission if you have separately received it. + +d) If the work has interactive user interfaces, each must display Appropriate +Legal Notices; however, if the Program has interactive interfaces that do +not display Appropriate Legal Notices, your work need not make them do so. + +A compilation of a covered work with other separate and independent works, +which are not by their nature extensions of the covered work, and which are +not combined with it such as to form a larger program, in or on a volume of +a storage or distribution medium, is called an "aggregate" if the compilation +and its resulting copyright are not used to limit the access or legal rights +of the compilation's users beyond what the individual works permit. Inclusion +of a covered work in an aggregate does not cause this License to apply to +the other parts of the aggregate. + + 6. Conveying Non-Source Forms. + +You may convey a covered work in object code form under the terms of sections +4 and 5, provided that you also convey the machine-readable Corresponding +Source under the terms of this License, in one of these ways: + +a) Convey the object code in, or embodied in, a physical product (including +a physical distribution medium), accompanied by the Corresponding Source fixed +on a durable physical medium customarily used for software interchange. + +b) Convey the object code in, or embodied in, a physical product (including +a physical distribution medium), accompanied by a written offer, valid for +at least three years and valid for as long as you offer spare parts or customer +support for that product model, to give anyone who possesses the object code +either (1) a copy of the Corresponding Source for all the software in the +product that is covered by this License, on a durable physical medium customarily +used for software interchange, for a price no more than your reasonable cost +of physically performing this conveying of source, or (2) access to copy the +Corresponding Source from a network server at no charge. + +c) Convey individual copies of the object code with a copy of the written +offer to provide the Corresponding Source. This alternative is allowed only +occasionally and noncommercially, and only if you received the object code +with such an offer, in accord with subsection 6b. + +d) Convey the object code by offering access from a designated place (gratis +or for a charge), and offer equivalent access to the Corresponding Source +in the same way through the same place at no further charge. You need not +require recipients to copy the Corresponding Source along with the object +code. If the place to copy the object code is a network server, the Corresponding +Source may be on a different server (operated by you or a third party) that +supports equivalent copying facilities, provided you maintain clear directions +next to the object code saying where to find the Corresponding Source. Regardless +of what server hosts the Corresponding Source, you remain obligated to ensure +that it is available for as long as needed to satisfy these requirements. + +e) Convey the object code using peer-to-peer transmission, provided you inform +other peers where the object code and Corresponding Source of the work are +being offered to the general public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded from +the Corresponding Source as a System Library, need not be included in conveying +the object code work. + +A "User Product" is either (1) a "consumer product", which means any tangible +personal property which is normally used for personal, family, or household +purposes, or (2) anything designed or sold for incorporation into a dwelling. +In determining whether a product is a consumer product, doubtful cases shall +be resolved in favor of coverage. For a particular product received by a particular +user, "normally used" refers to a typical or common use of that class of product, +regardless of the status of the particular user or of the way in which the +particular user actually uses, or expects or is expected to use, the product. +A product is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent the +only significant mode of use of the product. + +"Installation Information" for a User Product means any methods, procedures, +authorization keys, or other information required to install and execute modified +versions of a covered work in that User Product from a modified version of +its Corresponding Source. The information must suffice to ensure that the +continued functioning of the modified object code is in no case prevented +or interfered with solely because modification has been made. + +If you convey an object code work under this section in, or with, or specifically +for use in, a User Product, and the conveying occurs as part of a transaction +in which the right of possession and use of the User Product is transferred +to the recipient in perpetuity or for a fixed term (regardless of how the +transaction is characterized), the Corresponding Source conveyed under this +section must be accompanied by the Installation Information. But this requirement +does not apply if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has been installed +in ROM). + +The requirement to provide Installation Information does not include a requirement +to continue to provide support service, warranty, or updates for a work that +has been modified or installed by the recipient, or for the User Product in +which it has been modified or installed. Access to a network may be denied +when the modification itself materially and adversely affects the operation +of the network or violates the rules and protocols for communication across +the network. + +Corresponding Source conveyed, and Installation Information provided, in accord +with this section must be in a format that is publicly documented (and with +an implementation available to the public in source code form), and must require +no special password or key for unpacking, reading or copying. + + 7. Additional Terms. + +"Additional permissions" are terms that supplement the terms of this License +by making exceptions from one or more of its conditions. Additional permissions +that are applicable to the entire Program shall be treated as though they +were included in this License, to the extent that they are valid under applicable +law. If additional permissions apply only to part of the Program, that part +may be used separately under those permissions, but the entire Program remains +governed by this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option remove any +additional permissions from that copy, or from any part of it. (Additional +permissions may be written to require their own removal in certain cases when +you modify the work.) You may place additional permissions on material, added +by you to a covered work, for which you have or can give appropriate copyright +permission. + +Notwithstanding any other provision of this License, for material you add +to a covered work, you may (if authorized by the copyright holders of that +material) supplement the terms of this License with terms: + +a) Disclaiming warranty or limiting liability differently from the terms of +sections 15 and 16 of this License; or + +b) Requiring preservation of specified reasonable legal notices or author +attributions in that material or in the Appropriate Legal Notices displayed +by works containing it; or + +c) Prohibiting misrepresentation of the origin of that material, or requiring +that modified versions of such material be marked in reasonable ways as different +from the original version; or + +d) Limiting the use for publicity purposes of names of licensors or authors +of the material; or + +e) Declining to grant rights under trademark law for use of some trade names, +trademarks, or service marks; or + +f) Requiring indemnification of licensors and authors of that material by +anyone who conveys the material (or modified versions of it) with contractual +assumptions of liability to the recipient, for any liability that these contractual +assumptions directly impose on those licensors and authors. + +All other non-permissive additional terms are considered "further restrictions" +within the meaning of section 10. If the Program as you received it, or any +part of it, contains a notice stating that it is governed by this License +along with a term that is a further restriction, you may remove that term. +If a license document contains a further restriction but permits relicensing +or conveying under this License, you may add to a covered work material governed +by the terms of that license document, provided that the further restriction +does not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you must place, +in the relevant source files, a statement of the additional terms that apply +to those files, or a notice indicating where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the form +of a separately written license, or stated as exceptions; the above requirements +apply either way. + + 8. Termination. + +You may not propagate or modify a covered work except as expressly provided +under this License. Any attempt otherwise to propagate or modify it is void, +and will automatically terminate your rights under this License (including +any patent licenses granted under the third paragraph of section 11). + +However, if you cease all violation of this License, then your license from +a particular copyright holder is reinstated (a) provisionally, unless and +until the copyright holder explicitly and finally terminates your license, +and (b) permanently, if the copyright holder fails to notify you of the violation +by some reasonable means prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is reinstated permanently +if the copyright holder notifies you of the violation by some reasonable means, +this is the first time you have received notice of violation of this License +(for any work) from that copyright holder, and you cure the violation prior +to 30 days after your receipt of the notice. + +Termination of your rights under this section does not terminate the licenses +of parties who have received copies or rights from you under this License. +If your rights have been terminated and not permanently reinstated, you do +not qualify to receive new licenses for the same material under section 10. + + 9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or run a copy +of the Program. Ancillary propagation of a covered work occurring solely as +a consequence of using peer-to-peer transmission to receive a copy likewise +does not require acceptance. However, nothing other than this License grants +you permission to propagate or modify any covered work. These actions infringe +copyright if you do not accept this License. Therefore, by modifying or propagating +a covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically receives +a license from the original licensors, to run, modify and propagate that work, +subject to this License. You are not responsible for enforcing compliance +by third parties with this License. + +An "entity transaction" is a transaction transferring control of an organization, +or substantially all assets of one, or subdividing an organization, or merging +organizations. If propagation of a covered work results from an entity transaction, +each party to that transaction who receives a copy of the work also receives +whatever licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the Corresponding +Source of the work from the predecessor in interest, if the predecessor has +it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the rights +granted or affirmed under this License. For example, you may not impose a +license fee, royalty, or other charge for exercise of rights granted under +this License, and you may not initiate litigation (including a cross-claim +or counterclaim in a lawsuit) alleging that any patent claim is infringed +by making, using, selling, offering for sale, or importing the Program or +any portion of it. + + 11. Patents. + +A "contributor" is a copyright holder who authorizes use under this License +of the Program or a work on which the Program is based. The work thus licensed +is called the contributor's "contributor version". + +A contributor's "essential patent claims" are all patent claims owned or controlled +by the contributor, whether already acquired or hereafter acquired, that would +be infringed by some manner, permitted by this License, of making, using, +or selling its contributor version, but do not include claims that would be +infringed only as a consequence of further modification of the contributor +version. For purposes of this definition, "control" includes the right to +grant patent sublicenses in a manner consistent with the requirements of this +License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free patent +license under the contributor's essential patent claims, to make, use, sell, +offer for sale, import and otherwise run, modify and propagate the contents +of its contributor version. + +In the following three paragraphs, a "patent license" is any express agreement +or commitment, however denominated, not to enforce a patent (such as an express +permission to practice a patent or covenant not to sue for patent infringement). +To "grant" such a patent license to a party means to make such an agreement +or commitment not to enforce a patent against the party. + +If you convey a covered work, knowingly relying on a patent license, and the +Corresponding Source of the work is not available for anyone to copy, free +of charge and under the terms of this License, through a publicly available +network server or other readily accessible means, then you must either (1) +cause the Corresponding Source to be so available, or (2) arrange to deprive +yourself of the benefit of the patent license for this particular work, or +(3) arrange, in a manner consistent with the requirements of this License, +to extend the patent license to downstream recipients. "Knowingly relying" +means you have actual knowledge that, but for the patent license, your conveying +the covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that country +that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or arrangement, +you convey, or propagate by procuring conveyance of, a covered work, and grant +a patent license to some of the parties receiving the covered work authorizing +them to use, propagate, modify or convey a specific copy of the covered work, +then the patent license you grant is automatically extended to all recipients +of the covered work and works based on it. + +A patent license is "discriminatory" if it does not include within the scope +of its coverage, prohibits the exercise of, or is conditioned on the non-exercise +of one or more of the rights that are specifically granted under this License. +You may not convey a covered work if you are a party to an arrangement with +a third party that is in the business of distributing software, under which +you make payment to the third party based on the extent of your activity of +conveying the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory patent +license (a) in connection with copies of the covered work conveyed by you +(or copies made from those copies), or (b) primarily for and in connection +with specific products or compilations that contain the covered work, unless +you entered into that arrangement, or that patent license was granted, prior +to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting any implied +license or other defenses to infringement that may otherwise be available +to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or otherwise) +that contradict the conditions of this License, they do not excuse you from +the conditions of this License. If you cannot convey a covered work so as +to satisfy simultaneously your obligations under this License and any other +pertinent obligations, then as a consequence you may not convey it at all. +For example, if you agree to terms that obligate you to collect a royalty +for further conveying from those to whom you convey the Program, the only +way you could satisfy both those terms and this License would be to refrain +entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + +Notwithstanding any other provision of this License, you have permission to +link or combine any covered work with a work licensed under version 3 of the +GNU Affero General Public License into a single combined work, and to convey +the resulting work. The terms of this License will continue to apply to the +part which is the covered work, but the special requirements of the GNU Affero +General Public License, section 13, concerning interaction through a network +will apply to the combination as such. + + 14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions of the +GNU General Public License from time to time. Such new versions will be similar +in spirit to the present version, but may differ in detail to address new +problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies +that a certain numbered version of the GNU General Public License "or any +later version" applies to it, you have the option of following the terms and +conditions either of that numbered version or of any later version published +by the Free Software Foundation. If the Program does not specify a version +number of the GNU General Public License, you may choose any version ever +published by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future versions of +the GNU General Public License can be used, that proxy's public statement +of acceptance of a version permanently authorizes you to choose that version +for the Program. + +Later license versions may give you additional or different permissions. However, +no additional obligations are imposed on any author or copyright holder as +a result of your choosing to follow a later version. + + 15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE +LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER +EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM +PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR +CORRECTION. + + 16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL +ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM +AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, +INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO +USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED +INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE +PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER +PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided above cannot +be given local legal effect according to their terms, reviewing courts shall +apply local law that most closely approximates an absolute waiver of all civil +liability in connection with the Program, unless a warranty or assumption +of liability accompanies a copy of the Program in return for a fee. END OF +TERMS AND CONDITIONS + +How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible +use to the public, the best way to achieve this is to make it free software +which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach +them to the start of each source file to most effectively state the exclusion +of warranty; and each file should have at least the "copyright" line and a +pointer to where the full notice is found. + + + +Copyright (C) + +This program is free software: you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation, either version 3 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + +If the program does terminal interaction, make it output a short notice like +this when it starts in an interactive mode: + + Copyright (C) + +This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + +This is free software, and you are welcome to redistribute it under certain +conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands might +be different; for a GUI interface, you would use an "about box". + +You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. For +more information on this, and how to apply and follow the GNU GPL, see . + +The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General Public +License instead of this License. But first, please read . diff --git a/LICENSES/GPL-3.0-or-later.txt b/LICENSES/GPL-3.0-or-later.txt new file mode 100644 index 00000000..e142a525 --- /dev/null +++ b/LICENSES/GPL-3.0-or-later.txt @@ -0,0 +1,625 @@ +GNU GENERAL PUBLIC LICENSE + +Version 3, 29 June 2007 + +Copyright © 2007 Free Software Foundation, Inc. + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +Preamble + +The GNU General Public License is a free, copyleft license for software and +other kinds of works. + +The licenses for most software and other practical works are designed to take +away your freedom to share and change the works. By contrast, the GNU General +Public License is intended to guarantee your freedom to share and change all +versions of a program--to make sure it remains free software for all its users. +We, the Free Software Foundation, use the GNU General Public License for most +of our software; it applies also to any other work released this way by its +authors. You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our +General Public Licenses are designed to make sure that you have the freedom +to distribute copies of free software (and charge for them if you wish), that +you receive source code or can get it if you want it, that you can change +the software or use pieces of it in new free programs, and that you know you +can do these things. + +To protect your rights, we need to prevent others from denying you these rights +or asking you to surrender the rights. Therefore, you have certain responsibilities +if you distribute copies of the software, or if you modify it: responsibilities +to respect the freedom of others. + +For example, if you distribute copies of such a program, whether gratis or +for a fee, you must pass on to the recipients the same freedoms that you received. +You must make sure that they, too, receive or can get the source code. And +you must show them these terms so they know their rights. + +Developers that use the GNU GPL protect your rights with two steps: (1) assert +copyright on the software, and (2) offer you this License giving you legal +permission to copy, distribute and/or modify it. + +For the developers' and authors' protection, the GPL clearly explains that +there is no warranty for this free software. For both users' and authors' +sake, the GPL requires that modified versions be marked as changed, so that +their problems will not be attributed erroneously to authors of previous versions. + +Some devices are designed to deny users access to install or run modified +versions of the software inside them, although the manufacturer can do so. +This is fundamentally incompatible with the aim of protecting users' freedom +to change the software. The systematic pattern of such abuse occurs in the +area of products for individuals to use, which is precisely where it is most +unacceptable. Therefore, we have designed this version of the GPL to prohibit +the practice for those products. If such problems arise substantially in other +domains, we stand ready to extend this provision to those domains in future +versions of the GPL, as needed to protect the freedom of users. + +Finally, every program is threatened constantly by software patents. States +should not allow patents to restrict development and use of software on general-purpose +computers, but in those that do, we wish to avoid the special danger that +patents applied to a free program could make it effectively proprietary. To +prevent this, the GPL assures that patents cannot be used to render the program +non-free. + +The precise terms and conditions for copying, distribution and modification +follow. + +TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + +"Copyright" also means copyright-like laws that apply to other kinds of works, +such as semiconductor masks. + +"The Program" refers to any copyrightable work licensed under this License. +Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals +or organizations. + +To "modify" a work means to copy from or adapt all or part of the work in +a fashion requiring copyright permission, other than the making of an exact +copy. The resulting work is called a "modified version" of the earlier work +or a work "based on" the earlier work. + +A "covered work" means either the unmodified Program or a work based on the +Program. + +To "propagate" a work means to do anything with it that, without permission, +would make you directly or secondarily liable for infringement under applicable +copyright law, except executing it on a computer or modifying a private copy. +Propagation includes copying, distribution (with or without modification), +making available to the public, and in some countries other activities as +well. + +To "convey" a work means any kind of propagation that enables other parties +to make or receive copies. Mere interaction with a user through a computer +network, with no transfer of a copy, is not conveying. + +An interactive user interface displays "Appropriate Legal Notices" to the +extent that it includes a convenient and prominently visible feature that +(1) displays an appropriate copyright notice, and (2) tells the user that +there is no warranty for the work (except to the extent that warranties are +provided), that licensees may convey the work under this License, and how +to view a copy of this License. If the interface presents a list of user commands +or options, such as a menu, a prominent item in the list meets this criterion. + + 1. Source Code. + +The "source code" for a work means the preferred form of the work for making +modifications to it. "Object code" means any non-source form of a work. + +A "Standard Interface" means an interface that either is an official standard +defined by a recognized standards body, or, in the case of interfaces specified +for a particular programming language, one that is widely used among developers +working in that language. + +The "System Libraries" of an executable work include anything, other than +the work as a whole, that (a) is included in the normal form of packaging +a Major Component, but which is not part of that Major Component, and (b) +serves only to enable use of the work with that Major Component, or to implement +a Standard Interface for which an implementation is available to the public +in source code form. A "Major Component", in this context, means a major essential +component (kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to produce +the work, or an object code interpreter used to run it. + +The "Corresponding Source" for a work in object code form means all the source +code needed to generate, install, and (for an executable work) run the object +code and to modify the work, including scripts to control those activities. +However, it does not include the work's System Libraries, or general-purpose +tools or generally available free programs which are used unmodified in performing +those activities but which are not part of the work. For example, Corresponding +Source includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically linked +subprograms that the work is specifically designed to require, such as by +intimate data communication or control flow between those subprograms and +other parts of the work. + +The Corresponding Source need not include anything that users can regenerate +automatically from other parts of the Corresponding Source. + + The Corresponding Source for a work in source code form is that same work. + + 2. Basic Permissions. + +All rights granted under this License are granted for the term of copyright +on the Program, and are irrevocable provided the stated conditions are met. +This License explicitly affirms your unlimited permission to run the unmodified +Program. The output from running a covered work is covered by this License +only if the output, given its content, constitutes a covered work. This License +acknowledges your rights of fair use or other equivalent, as provided by copyright +law. + +You may make, run and propagate covered works that you do not convey, without +conditions so long as your license otherwise remains in force. You may convey +covered works to others for the sole purpose of having them make modifications +exclusively for you, or provide you with facilities for running those works, +provided that you comply with the terms of this License in conveying all material +for which you do not control copyright. Those thus making or running the covered +works for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of your copyrighted +material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the conditions +stated below. Sublicensing is not allowed; section 10 makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + +No covered work shall be deemed part of an effective technological measure +under any applicable law fulfilling obligations under article 11 of the WIPO +copyright treaty adopted on 20 December 1996, or similar laws prohibiting +or restricting circumvention of such measures. + +When you convey a covered work, you waive any legal power to forbid circumvention +of technological measures to the extent such circumvention is effected by +exercising rights under this License with respect to the covered work, and +you disclaim any intention to limit operation or modification of the work +as a means of enforcing, against the work's users, your or third parties' +legal rights to forbid circumvention of technological measures. + + 4. Conveying Verbatim Copies. + +You may convey verbatim copies of the Program's source code as you receive +it, in any medium, provided that you conspicuously and appropriately publish +on each copy an appropriate copyright notice; keep intact all notices stating +that this License and any non-permissive terms added in accord with section +7 apply to the code; keep intact all notices of the absence of any warranty; +and give all recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, and you +may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + +You may convey a work based on the Program, or the modifications to produce +it from the Program, in the form of source code under the terms of section +4, provided that you also meet all of these conditions: + +a) The work must carry prominent notices stating that you modified it, and +giving a relevant date. + +b) The work must carry prominent notices stating that it is released under +this License and any conditions added under section 7. This requirement modifies +the requirement in section 4 to "keep intact all notices". + +c) You must license the entire work, as a whole, under this License to anyone +who comes into possession of a copy. This License will therefore apply, along +with any applicable section 7 additional terms, to the whole of the work, +and all its parts, regardless of how they are packaged. This License gives +no permission to license the work in any other way, but it does not invalidate +such permission if you have separately received it. + +d) If the work has interactive user interfaces, each must display Appropriate +Legal Notices; however, if the Program has interactive interfaces that do +not display Appropriate Legal Notices, your work need not make them do so. + +A compilation of a covered work with other separate and independent works, +which are not by their nature extensions of the covered work, and which are +not combined with it such as to form a larger program, in or on a volume of +a storage or distribution medium, is called an "aggregate" if the compilation +and its resulting copyright are not used to limit the access or legal rights +of the compilation's users beyond what the individual works permit. Inclusion +of a covered work in an aggregate does not cause this License to apply to +the other parts of the aggregate. + + 6. Conveying Non-Source Forms. + +You may convey a covered work in object code form under the terms of sections +4 and 5, provided that you also convey the machine-readable Corresponding +Source under the terms of this License, in one of these ways: + +a) Convey the object code in, or embodied in, a physical product (including +a physical distribution medium), accompanied by the Corresponding Source fixed +on a durable physical medium customarily used for software interchange. + +b) Convey the object code in, or embodied in, a physical product (including +a physical distribution medium), accompanied by a written offer, valid for +at least three years and valid for as long as you offer spare parts or customer +support for that product model, to give anyone who possesses the object code +either (1) a copy of the Corresponding Source for all the software in the +product that is covered by this License, on a durable physical medium customarily +used for software interchange, for a price no more than your reasonable cost +of physically performing this conveying of source, or (2) access to copy the +Corresponding Source from a network server at no charge. + +c) Convey individual copies of the object code with a copy of the written +offer to provide the Corresponding Source. This alternative is allowed only +occasionally and noncommercially, and only if you received the object code +with such an offer, in accord with subsection 6b. + +d) Convey the object code by offering access from a designated place (gratis +or for a charge), and offer equivalent access to the Corresponding Source +in the same way through the same place at no further charge. You need not +require recipients to copy the Corresponding Source along with the object +code. If the place to copy the object code is a network server, the Corresponding +Source may be on a different server (operated by you or a third party) that +supports equivalent copying facilities, provided you maintain clear directions +next to the object code saying where to find the Corresponding Source. Regardless +of what server hosts the Corresponding Source, you remain obligated to ensure +that it is available for as long as needed to satisfy these requirements. + +e) Convey the object code using peer-to-peer transmission, provided you inform +other peers where the object code and Corresponding Source of the work are +being offered to the general public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded from +the Corresponding Source as a System Library, need not be included in conveying +the object code work. + +A "User Product" is either (1) a "consumer product", which means any tangible +personal property which is normally used for personal, family, or household +purposes, or (2) anything designed or sold for incorporation into a dwelling. +In determining whether a product is a consumer product, doubtful cases shall +be resolved in favor of coverage. For a particular product received by a particular +user, "normally used" refers to a typical or common use of that class of product, +regardless of the status of the particular user or of the way in which the +particular user actually uses, or expects or is expected to use, the product. +A product is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent the +only significant mode of use of the product. + +"Installation Information" for a User Product means any methods, procedures, +authorization keys, or other information required to install and execute modified +versions of a covered work in that User Product from a modified version of +its Corresponding Source. The information must suffice to ensure that the +continued functioning of the modified object code is in no case prevented +or interfered with solely because modification has been made. + +If you convey an object code work under this section in, or with, or specifically +for use in, a User Product, and the conveying occurs as part of a transaction +in which the right of possession and use of the User Product is transferred +to the recipient in perpetuity or for a fixed term (regardless of how the +transaction is characterized), the Corresponding Source conveyed under this +section must be accompanied by the Installation Information. But this requirement +does not apply if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has been installed +in ROM). + +The requirement to provide Installation Information does not include a requirement +to continue to provide support service, warranty, or updates for a work that +has been modified or installed by the recipient, or for the User Product in +which it has been modified or installed. Access to a network may be denied +when the modification itself materially and adversely affects the operation +of the network or violates the rules and protocols for communication across +the network. + +Corresponding Source conveyed, and Installation Information provided, in accord +with this section must be in a format that is publicly documented (and with +an implementation available to the public in source code form), and must require +no special password or key for unpacking, reading or copying. + + 7. Additional Terms. + +"Additional permissions" are terms that supplement the terms of this License +by making exceptions from one or more of its conditions. Additional permissions +that are applicable to the entire Program shall be treated as though they +were included in this License, to the extent that they are valid under applicable +law. If additional permissions apply only to part of the Program, that part +may be used separately under those permissions, but the entire Program remains +governed by this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option remove any +additional permissions from that copy, or from any part of it. (Additional +permissions may be written to require their own removal in certain cases when +you modify the work.) You may place additional permissions on material, added +by you to a covered work, for which you have or can give appropriate copyright +permission. + +Notwithstanding any other provision of this License, for material you add +to a covered work, you may (if authorized by the copyright holders of that +material) supplement the terms of this License with terms: + +a) Disclaiming warranty or limiting liability differently from the terms of +sections 15 and 16 of this License; or + +b) Requiring preservation of specified reasonable legal notices or author +attributions in that material or in the Appropriate Legal Notices displayed +by works containing it; or + +c) Prohibiting misrepresentation of the origin of that material, or requiring +that modified versions of such material be marked in reasonable ways as different +from the original version; or + +d) Limiting the use for publicity purposes of names of licensors or authors +of the material; or + +e) Declining to grant rights under trademark law for use of some trade names, +trademarks, or service marks; or + +f) Requiring indemnification of licensors and authors of that material by +anyone who conveys the material (or modified versions of it) with contractual +assumptions of liability to the recipient, for any liability that these contractual +assumptions directly impose on those licensors and authors. + +All other non-permissive additional terms are considered "further restrictions" +within the meaning of section 10. If the Program as you received it, or any +part of it, contains a notice stating that it is governed by this License +along with a term that is a further restriction, you may remove that term. +If a license document contains a further restriction but permits relicensing +or conveying under this License, you may add to a covered work material governed +by the terms of that license document, provided that the further restriction +does not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you must place, +in the relevant source files, a statement of the additional terms that apply +to those files, or a notice indicating where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the form +of a separately written license, or stated as exceptions; the above requirements +apply either way. + + 8. Termination. + +You may not propagate or modify a covered work except as expressly provided +under this License. Any attempt otherwise to propagate or modify it is void, +and will automatically terminate your rights under this License (including +any patent licenses granted under the third paragraph of section 11). + +However, if you cease all violation of this License, then your license from +a particular copyright holder is reinstated (a) provisionally, unless and +until the copyright holder explicitly and finally terminates your license, +and (b) permanently, if the copyright holder fails to notify you of the violation +by some reasonable means prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is reinstated permanently +if the copyright holder notifies you of the violation by some reasonable means, +this is the first time you have received notice of violation of this License +(for any work) from that copyright holder, and you cure the violation prior +to 30 days after your receipt of the notice. + +Termination of your rights under this section does not terminate the licenses +of parties who have received copies or rights from you under this License. +If your rights have been terminated and not permanently reinstated, you do +not qualify to receive new licenses for the same material under section 10. + + 9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or run a copy +of the Program. Ancillary propagation of a covered work occurring solely as +a consequence of using peer-to-peer transmission to receive a copy likewise +does not require acceptance. However, nothing other than this License grants +you permission to propagate or modify any covered work. These actions infringe +copyright if you do not accept this License. Therefore, by modifying or propagating +a covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically receives +a license from the original licensors, to run, modify and propagate that work, +subject to this License. You are not responsible for enforcing compliance +by third parties with this License. + +An "entity transaction" is a transaction transferring control of an organization, +or substantially all assets of one, or subdividing an organization, or merging +organizations. If propagation of a covered work results from an entity transaction, +each party to that transaction who receives a copy of the work also receives +whatever licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the Corresponding +Source of the work from the predecessor in interest, if the predecessor has +it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the rights +granted or affirmed under this License. For example, you may not impose a +license fee, royalty, or other charge for exercise of rights granted under +this License, and you may not initiate litigation (including a cross-claim +or counterclaim in a lawsuit) alleging that any patent claim is infringed +by making, using, selling, offering for sale, or importing the Program or +any portion of it. + + 11. Patents. + +A "contributor" is a copyright holder who authorizes use under this License +of the Program or a work on which the Program is based. The work thus licensed +is called the contributor's "contributor version". + +A contributor's "essential patent claims" are all patent claims owned or controlled +by the contributor, whether already acquired or hereafter acquired, that would +be infringed by some manner, permitted by this License, of making, using, +or selling its contributor version, but do not include claims that would be +infringed only as a consequence of further modification of the contributor +version. For purposes of this definition, "control" includes the right to +grant patent sublicenses in a manner consistent with the requirements of this +License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free patent +license under the contributor's essential patent claims, to make, use, sell, +offer for sale, import and otherwise run, modify and propagate the contents +of its contributor version. + +In the following three paragraphs, a "patent license" is any express agreement +or commitment, however denominated, not to enforce a patent (such as an express +permission to practice a patent or covenant not to sue for patent infringement). +To "grant" such a patent license to a party means to make such an agreement +or commitment not to enforce a patent against the party. + +If you convey a covered work, knowingly relying on a patent license, and the +Corresponding Source of the work is not available for anyone to copy, free +of charge and under the terms of this License, through a publicly available +network server or other readily accessible means, then you must either (1) +cause the Corresponding Source to be so available, or (2) arrange to deprive +yourself of the benefit of the patent license for this particular work, or +(3) arrange, in a manner consistent with the requirements of this License, +to extend the patent license to downstream recipients. "Knowingly relying" +means you have actual knowledge that, but for the patent license, your conveying +the covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that country +that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or arrangement, +you convey, or propagate by procuring conveyance of, a covered work, and grant +a patent license to some of the parties receiving the covered work authorizing +them to use, propagate, modify or convey a specific copy of the covered work, +then the patent license you grant is automatically extended to all recipients +of the covered work and works based on it. + +A patent license is "discriminatory" if it does not include within the scope +of its coverage, prohibits the exercise of, or is conditioned on the non-exercise +of one or more of the rights that are specifically granted under this License. +You may not convey a covered work if you are a party to an arrangement with +a third party that is in the business of distributing software, under which +you make payment to the third party based on the extent of your activity of +conveying the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory patent +license (a) in connection with copies of the covered work conveyed by you +(or copies made from those copies), or (b) primarily for and in connection +with specific products or compilations that contain the covered work, unless +you entered into that arrangement, or that patent license was granted, prior +to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting any implied +license or other defenses to infringement that may otherwise be available +to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or otherwise) +that contradict the conditions of this License, they do not excuse you from +the conditions of this License. If you cannot convey a covered work so as +to satisfy simultaneously your obligations under this License and any other +pertinent obligations, then as a consequence you may not convey it at all. +For example, if you agree to terms that obligate you to collect a royalty +for further conveying from those to whom you convey the Program, the only +way you could satisfy both those terms and this License would be to refrain +entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + +Notwithstanding any other provision of this License, you have permission to +link or combine any covered work with a work licensed under version 3 of the +GNU Affero General Public License into a single combined work, and to convey +the resulting work. The terms of this License will continue to apply to the +part which is the covered work, but the special requirements of the GNU Affero +General Public License, section 13, concerning interaction through a network +will apply to the combination as such. + + 14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions of the +GNU General Public License from time to time. Such new versions will be similar +in spirit to the present version, but may differ in detail to address new +problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies +that a certain numbered version of the GNU General Public License "or any +later version" applies to it, you have the option of following the terms and +conditions either of that numbered version or of any later version published +by the Free Software Foundation. If the Program does not specify a version +number of the GNU General Public License, you may choose any version ever +published by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future versions of +the GNU General Public License can be used, that proxy's public statement +of acceptance of a version permanently authorizes you to choose that version +for the Program. + +Later license versions may give you additional or different permissions. However, +no additional obligations are imposed on any author or copyright holder as +a result of your choosing to follow a later version. + + 15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE +LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER +EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM +PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR +CORRECTION. + + 16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL +ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM +AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, +INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO +USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED +INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE +PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER +PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided above cannot +be given local legal effect according to their terms, reviewing courts shall +apply local law that most closely approximates an absolute waiver of all civil +liability in connection with the Program, unless a warranty or assumption +of liability accompanies a copy of the Program in return for a fee. END OF +TERMS AND CONDITIONS + +How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible +use to the public, the best way to achieve this is to make it free software +which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach +them to the start of each source file to most effectively state the exclusion +of warranty; and each file should have at least the "copyright" line and a +pointer to where the full notice is found. + + + +Copyright (C) + +This program is free software: you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation, either version 3 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + +If the program does terminal interaction, make it output a short notice like +this when it starts in an interactive mode: + + Copyright (C) + +This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + +This is free software, and you are welcome to redistribute it under certain +conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands might +be different; for a GUI interface, you would use an "about box". + +You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. For +more information on this, and how to apply and follow the GNU GPL, see . + +The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General Public +License instead of this License. But first, please read . diff --git a/LICENSES/LGPL-2.0-only.txt b/LICENSES/LGPL-2.0-only.txt new file mode 100644 index 00000000..5c96471a --- /dev/null +++ b/LICENSES/LGPL-2.0-only.txt @@ -0,0 +1,446 @@ +GNU LIBRARY GENERAL PUBLIC LICENSE + +Version 2, June 1991 Copyright (C) 1991 Free Software Foundation, Inc. + +51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is numbered 2 because +it goes with version 2 of the ordinary GPL.] + +Preamble + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public Licenses are intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. + +This license, the Library General Public License, applies to some specially +designated Free Software Foundation software, and to any other libraries whose +authors decide to use it. You can use it for your libraries, too. + +When we speak of free software, we are referring to freedom, not price. Our +General Public Licenses are designed to make sure that you have the freedom +to distribute copies of free software (and charge for this service if you +wish), that you receive source code or can get it if you want it, that you +can change the software or use pieces of it in new free programs; and that +you know you can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to +deny you these rights or to ask you to surrender the rights. These restrictions +translate to certain responsibilities for you if you distribute copies of +the library, or if you modify it. + +For example, if you distribute copies of the library, whether gratis or for +a fee, you must give the recipients all the rights that we gave you. You must +make sure that they, too, receive or can get the source code. If you link +a program with the library, you must provide complete object files to the +recipients so that they can relink them with the library, after making changes +to the library and recompiling it. And you must show them these terms so they +know their rights. + +Our method of protecting your rights has two steps: (1) copyright the library, +and (2) offer you this license which gives you legal permission to copy, distribute +and/or modify the library. + +Also, for each distributor's protection, we want to make certain that everyone +understands that there is no warranty for this free library. If the library +is modified by someone else and passed on, we want its recipients to know +that what they have is not the original version, so that any problems introduced +by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We +wish to avoid the danger that companies distributing free software will individually +obtain patent licenses, thus in effect transforming the program into proprietary +software. To prevent this, we have made it clear that any patent must be licensed +for everyone's free use or not licensed at all. + +Most GNU software, including some libraries, is covered by the ordinary GNU +General Public License, which was designed for utility programs. This license, +the GNU Library General Public License, applies to certain designated libraries. +This license is quite different from the ordinary one; be sure to read it +in full, and don't assume that anything in it is the same as in the ordinary +license. + +The reason we have a separate public license for some libraries is that they +blur the distinction we usually make between modifying or adding to a program +and simply using it. Linking a program with a library, without changing the +library, is in some sense simply using the library, and is analogous to running +a utility program or application program. However, in a textual and legal +sense, the linked executable is a combined work, a derivative of the original +library, and the ordinary General Public License treats it as such. + +Because of this blurred distinction, using the ordinary General Public License +for libraries did not effectively promote software sharing, because most developers +did not use the libraries. We concluded that weaker conditions might promote +sharing better. + +However, unrestricted linking of non-free programs would deprive the users +of those programs of all benefit from the free status of the libraries themselves. +This Library General Public License is intended to permit developers of non-free +programs to use free libraries, while preserving your freedom as a user of +such programs to change the free libraries that are incorporated in them. +(We have not seen how to achieve this as regards changes in header files, +but we have achieved it as regards changes in the actual functions of the +Library.) The hope is that this will lead to faster development of free libraries. + +The precise terms and conditions for copying, distribution and modification +follow. Pay close attention to the difference between a "work based on the +library" and a "work that uses the library". The former contains code derived +from the library, while the latter only works together with the library. + +Note that it is possible for a library to be covered by the ordinary General +Public License rather than by this special one. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License Agreement applies to any software library which contains a +notice placed by the copyright holder or other authorized party saying it +may be distributed under the terms of this Library General Public License +(also called "this License"). Each licensee is addressed as "you". + +A "library" means a collection of software functions and/or data prepared +so as to be conveniently linked with application programs (which use some +of those functions and data) to form executables. + +The "Library", below, refers to any such software library or work which has +been distributed under these terms. A "work based on the Library" means either +the Library or any derivative work under copyright law: that is to say, a +work containing the Library or a portion of it, either verbatim or with modifications +and/or translated straightforwardly into another language. (Hereinafter, translation +is included without limitation in the term "modification".) + +"Source code" for a work means the preferred form of the work for making modifications +to it. For a library, complete source code means all the source code for all +modules it contains, plus any associated interface definition files, plus +the scripts used to control compilation and installation of the library. + +Activities other than copying, distribution and modification are not covered +by this License; they are outside its scope. The act of running a program +using the Library is not restricted, and output from such a program is covered +only if its contents constitute a work based on the Library (independent of +the use of the Library in a tool for writing it). Whether that is true depends +on what the Library does and what the program that uses the Library does. + +1. You may copy and distribute verbatim copies of the Library's complete source +code as you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and disclaimer +of warranty; keep intact all the notices that refer to this License and to +the absence of any warranty; and distribute a copy of this License along with +the Library. + +You may charge a fee for the physical act of transferring a copy, and you +may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Library or any portion of it, +thus forming a work based on the Library, and copy and distribute such modifications +or work under the terms of Section 1 above, provided that you also meet all +of these conditions: + + a) The modified work must itself be a software library. + +b) You must cause the files modified to carry prominent notices stating that +you changed the files and the date of any change. + +c) You must cause the whole of the work to be licensed at no charge to all +third parties under the terms of this License. + +d) If a facility in the modified Library refers to a function or a table of +data to be supplied by an application program that uses the facility, other +than as an argument passed when the facility is invoked, then you must make +a good faith effort to ensure that, in the event an application does not supply +such function or table, the facility still operates, and performs whatever +part of its purpose remains meaningful. + +(For example, a function in a library to compute square roots has a purpose +that is entirely well-defined independent of the application. Therefore, Subsection +2d requires that any application-supplied function or table used by this function +must be optional: if the application does not supply it, the square root function +must still compute square roots.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Library, and can be reasonably +considered independent and separate works in themselves, then this License, +and its terms, do not apply to those sections when you distribute them as +separate works. But when you distribute the same sections as part of a whole +which is a work based on the Library, the distribution of the whole must be +on the terms of this License, whose permissions for other licensees extend +to the entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest your +rights to work written entirely by you; rather, the intent is to exercise +the right to control the distribution of derivative or collective works based +on the Library. + +In addition, mere aggregation of another work not based on the Library with +the Library (or with a work based on the Library) on a volume of a storage +or distribution medium does not bring the other work under the scope of this +License. + +3. You may opt to apply the terms of the ordinary GNU General Public License +instead of this License to a given copy of the Library. To do this, you must +alter all the notices that refer to this License, so that they refer to the +ordinary GNU General Public License, version 2, instead of to this License. +(If a newer version than version 2 of the ordinary GNU General Public License +has appeared, then you can specify that version instead if you wish.) Do not +make any other change in these notices. + +Once this change is made in a given copy, it is irreversible for that copy, +so the ordinary GNU General Public License applies to all subsequent copies +and derivative works made from that copy. + +This option is useful when you wish to copy part of the code of the Library +into a program that is not a library. + +4. You may copy and distribute the Library (or a portion or derivative of +it, under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you accompany it with the complete corresponding +machine-readable source code, which must be distributed under the terms of +Sections 1 and 2 above on a medium customarily used for software interchange. + +If distribution of object code is made by offering access to copy from a designated +place, then offering equivalent access to copy the source code from the same +place satisfies the requirement to distribute the source code, even though +third parties are not compelled to copy the source along with the object code. + +5. A program that contains no derivative of any portion of the Library, but +is designed to work with the Library by being compiled or linked with it, +is called a "work that uses the Library". Such a work, in isolation, is not +a derivative work of the Library, and therefore falls outside the scope of +this License. + +However, linking a "work that uses the Library" with the Library creates an +executable that is a derivative of the Library (because it contains portions +of the Library), rather than a "work that uses the library". The executable +is therefore covered by this License. Section 6 states terms for distribution +of such executables. + +When a "work that uses the Library" uses material from a header file that +is part of the Library, the object code for the work may be a derivative work +of the Library even though the source code is not. Whether this is true is +especially significant if the work can be linked without the Library, or if +the work is itself a library. The threshold for this to be true is not precisely +defined by law. + +If such an object file uses only numerical parameters, data structure layouts +and accessors, and small macros and small inline functions (ten lines or less +in length), then the use of the object file is unrestricted, regardless of +whether it is legally a derivative work. (Executables containing this object +code plus portions of the Library will still fall under Section 6.) + +Otherwise, if the work is a derivative of the Library, you may distribute +the object code for the work under the terms of Section 6. Any executables +containing that work also fall under Section 6, whether or not they are linked +directly with the Library itself. + +6. As an exception to the Sections above, you may also compile or link a "work +that uses the Library" with the Library to produce a work containing portions +of the Library, and distribute that work under terms of your choice, provided +that the terms permit modification of the work for the customer's own use +and reverse engineering for debugging such modifications. + +You must give prominent notice with each copy of the work that the Library +is used in it and that the Library and its use are covered by this License. +You must supply a copy of this License. If the work during execution displays +copyright notices, you must include the copyright notice for the Library among +them, as well as a reference directing the user to the copy of this License. +Also, you must do one of these things: + +a) Accompany the work with the complete corresponding machine-readable source +code for the Library including whatever changes were used in the work (which +must be distributed under Sections 1 and 2 above); and, if the work is an +executable linked with the Library, with the complete machine-readable "work +that uses the Library", as object code and/or source code, so that the user +can modify the Library and then relink to produce a modified executable containing +the modified Library. (It is understood that the user who changes the contents +of definitions files in the Library will not necessarily be able to recompile +the application to use the modified definitions.) + +b) Accompany the work with a written offer, valid for at least three years, +to give the same user the materials specified in Subsection 6a, above, for +a charge no more than the cost of performing this distribution. + +c) If distribution of the work is made by offering access to copy from a designated +place, offer equivalent access to copy the above specified materials from +the same place. + +d) Verify that the user has already received a copy of these materials or +that you have already sent this user a copy. + +For an executable, the required form of the "work that uses the Library" must +include any data and utility programs needed for reproducing the executable +from it. However, as a special exception, the source code distributed need +not include anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the operating +system on which the executable runs, unless that component itself accompanies +the executable. + +It may happen that this requirement contradicts the license restrictions of +other proprietary libraries that do not normally accompany the operating system. +Such a contradiction means you cannot use both them and the Library together +in an executable that you distribute. + +7. You may place library facilities that are a work based on the Library side-by-side +in a single library together with other library facilities not covered by +this License, and distribute such a combined library, provided that the separate +distribution of the work based on the Library and of the other library facilities +is otherwise permitted, and provided that you do these two things: + +a) Accompany the combined library with a copy of the same work based on the +Library, uncombined with any other library facilities. This must be distributed +under the terms of the Sections above. + +b) Give prominent notice with the combined library of the fact that part of +it is a work based on the Library, and explaining where to find the accompanying +uncombined form of the same work. + +8. You may not copy, modify, sublicense, link with, or distribute the Library +except as expressly provided under this License. Any attempt otherwise to +copy, modify, sublicense, link with, or distribute the Library is void, and +will automatically terminate your rights under this License. However, parties +who have received copies, or rights, from you under this License will not +have their licenses terminated so long as such parties remain in full compliance. + +9. You are not required to accept this License, since you have not signed +it. However, nothing else grants you permission to modify or distribute the +Library or its derivative works. These actions are prohibited by law if you +do not accept this License. Therefore, by modifying or distributing the Library +(or any work based on the Library), you indicate your acceptance of this License +to do so, and all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + +10. Each time you redistribute the Library (or any work based on the Library), +the recipient automatically receives a license from the original licensor +to copy, distribute, link with or modify the Library subject to these terms +and conditions. You may not impose any further restrictions on the recipients' +exercise of the rights granted herein. You are not responsible for enforcing +compliance by third parties to this License. + +11. If, as a consequence of a court judgment or allegation of patent infringement +or for any other reason (not limited to patent issues), conditions are imposed +on you (whether by court order, agreement or otherwise) that contradict the +conditions of this License, they do not excuse you from the conditions of +this License. If you cannot distribute so as to satisfy simultaneously your +obligations under this License and any other pertinent obligations, then as +a consequence you may not distribute the Library at all. For example, if a +patent license would not permit royalty-free redistribution of the Library +by all those who receive copies directly or indirectly through you, then the +only way you could satisfy both it and this License would be to refrain entirely +from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents +or other property right claims or to contest validity of any such claims; +this section has the sole purpose of protecting the integrity of the free +software distribution system which is implemented by public license practices. +Many people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose +that choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +12. If the distribution and/or use of the Library is restricted in certain +countries either by patents or by copyrighted interfaces, the original copyright +holder who places the Library under this License may add an explicit geographical +distribution limitation excluding those countries, so that distribution is +permitted only in or among countries not thus excluded. In such case, this +License incorporates the limitation as if written in the body of this License. + +13. The Free Software Foundation may publish revised and/or new versions of +the Library General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to address +new problems or concerns. + +Each version is given a distinguishing version number. If the Library specifies +a version number of this License which applies to it and "any later version", +you have the option of following the terms and conditions either of that version +or of any later version published by the Free Software Foundation. If the +Library does not specify a license version number, you may choose any version +ever published by the Free Software Foundation. + +14. If you wish to incorporate parts of the Library into other free programs +whose distribution conditions are incompatible with these, write to the author +to ask for permission. For software which is copyrighted by the Free Software +Foundation, write to the Free Software Foundation; we sometimes make exceptions +for this. Our decision will be guided by the two goals of preserving the free +status of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + +15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR +THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE +STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY +"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE +OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE +THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE +OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA +OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES +OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH +HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Libraries + +If you develop a new library, and you want it to be of the greatest possible +use to the public, we recommend making it free software that everyone can +redistribute and change. You can do so by permitting redistribution under +these terms (or, alternatively, under the terms of the ordinary General Public +License). + +To apply these terms, attach the following notices to the library. It is safest +to attach them to the start of each source file to most effectively convey +the exclusion of warranty; and each file should have at least the "copyright" +line and a pointer to where the full notice is found. + +one line to give the library's name and an idea of what it does. + +Copyright (C) year name of author + +This library is free software; you can redistribute it and/or modify it under +the terms of the GNU Library General Public License as published by the Free +Software Foundation; either version 2 of the License, or (at your option) +any later version. + +This library is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more +details. + +You should have received a copy of the GNU Library General Public License +along with this library; if not, write to the Free Software Foundation, Inc., +51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your school, +if any, to sign a "copyright disclaimer" for the library, if necessary. Here +is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in + +the library `Frob' (a library for tweaking knobs) written + +by James Random Hacker. + +signature of Ty Coon, 1 April 1990 + +Ty Coon, President of Vice + +That's all there is to it! diff --git a/LICENSES/LGPL-2.0-or-later.txt b/LICENSES/LGPL-2.0-or-later.txt new file mode 100644 index 00000000..5c96471a --- /dev/null +++ b/LICENSES/LGPL-2.0-or-later.txt @@ -0,0 +1,446 @@ +GNU LIBRARY GENERAL PUBLIC LICENSE + +Version 2, June 1991 Copyright (C) 1991 Free Software Foundation, Inc. + +51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is numbered 2 because +it goes with version 2 of the ordinary GPL.] + +Preamble + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public Licenses are intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. + +This license, the Library General Public License, applies to some specially +designated Free Software Foundation software, and to any other libraries whose +authors decide to use it. You can use it for your libraries, too. + +When we speak of free software, we are referring to freedom, not price. Our +General Public Licenses are designed to make sure that you have the freedom +to distribute copies of free software (and charge for this service if you +wish), that you receive source code or can get it if you want it, that you +can change the software or use pieces of it in new free programs; and that +you know you can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to +deny you these rights or to ask you to surrender the rights. These restrictions +translate to certain responsibilities for you if you distribute copies of +the library, or if you modify it. + +For example, if you distribute copies of the library, whether gratis or for +a fee, you must give the recipients all the rights that we gave you. You must +make sure that they, too, receive or can get the source code. If you link +a program with the library, you must provide complete object files to the +recipients so that they can relink them with the library, after making changes +to the library and recompiling it. And you must show them these terms so they +know their rights. + +Our method of protecting your rights has two steps: (1) copyright the library, +and (2) offer you this license which gives you legal permission to copy, distribute +and/or modify the library. + +Also, for each distributor's protection, we want to make certain that everyone +understands that there is no warranty for this free library. If the library +is modified by someone else and passed on, we want its recipients to know +that what they have is not the original version, so that any problems introduced +by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We +wish to avoid the danger that companies distributing free software will individually +obtain patent licenses, thus in effect transforming the program into proprietary +software. To prevent this, we have made it clear that any patent must be licensed +for everyone's free use or not licensed at all. + +Most GNU software, including some libraries, is covered by the ordinary GNU +General Public License, which was designed for utility programs. This license, +the GNU Library General Public License, applies to certain designated libraries. +This license is quite different from the ordinary one; be sure to read it +in full, and don't assume that anything in it is the same as in the ordinary +license. + +The reason we have a separate public license for some libraries is that they +blur the distinction we usually make between modifying or adding to a program +and simply using it. Linking a program with a library, without changing the +library, is in some sense simply using the library, and is analogous to running +a utility program or application program. However, in a textual and legal +sense, the linked executable is a combined work, a derivative of the original +library, and the ordinary General Public License treats it as such. + +Because of this blurred distinction, using the ordinary General Public License +for libraries did not effectively promote software sharing, because most developers +did not use the libraries. We concluded that weaker conditions might promote +sharing better. + +However, unrestricted linking of non-free programs would deprive the users +of those programs of all benefit from the free status of the libraries themselves. +This Library General Public License is intended to permit developers of non-free +programs to use free libraries, while preserving your freedom as a user of +such programs to change the free libraries that are incorporated in them. +(We have not seen how to achieve this as regards changes in header files, +but we have achieved it as regards changes in the actual functions of the +Library.) The hope is that this will lead to faster development of free libraries. + +The precise terms and conditions for copying, distribution and modification +follow. Pay close attention to the difference between a "work based on the +library" and a "work that uses the library". The former contains code derived +from the library, while the latter only works together with the library. + +Note that it is possible for a library to be covered by the ordinary General +Public License rather than by this special one. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License Agreement applies to any software library which contains a +notice placed by the copyright holder or other authorized party saying it +may be distributed under the terms of this Library General Public License +(also called "this License"). Each licensee is addressed as "you". + +A "library" means a collection of software functions and/or data prepared +so as to be conveniently linked with application programs (which use some +of those functions and data) to form executables. + +The "Library", below, refers to any such software library or work which has +been distributed under these terms. A "work based on the Library" means either +the Library or any derivative work under copyright law: that is to say, a +work containing the Library or a portion of it, either verbatim or with modifications +and/or translated straightforwardly into another language. (Hereinafter, translation +is included without limitation in the term "modification".) + +"Source code" for a work means the preferred form of the work for making modifications +to it. For a library, complete source code means all the source code for all +modules it contains, plus any associated interface definition files, plus +the scripts used to control compilation and installation of the library. + +Activities other than copying, distribution and modification are not covered +by this License; they are outside its scope. The act of running a program +using the Library is not restricted, and output from such a program is covered +only if its contents constitute a work based on the Library (independent of +the use of the Library in a tool for writing it). Whether that is true depends +on what the Library does and what the program that uses the Library does. + +1. You may copy and distribute verbatim copies of the Library's complete source +code as you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and disclaimer +of warranty; keep intact all the notices that refer to this License and to +the absence of any warranty; and distribute a copy of this License along with +the Library. + +You may charge a fee for the physical act of transferring a copy, and you +may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Library or any portion of it, +thus forming a work based on the Library, and copy and distribute such modifications +or work under the terms of Section 1 above, provided that you also meet all +of these conditions: + + a) The modified work must itself be a software library. + +b) You must cause the files modified to carry prominent notices stating that +you changed the files and the date of any change. + +c) You must cause the whole of the work to be licensed at no charge to all +third parties under the terms of this License. + +d) If a facility in the modified Library refers to a function or a table of +data to be supplied by an application program that uses the facility, other +than as an argument passed when the facility is invoked, then you must make +a good faith effort to ensure that, in the event an application does not supply +such function or table, the facility still operates, and performs whatever +part of its purpose remains meaningful. + +(For example, a function in a library to compute square roots has a purpose +that is entirely well-defined independent of the application. Therefore, Subsection +2d requires that any application-supplied function or table used by this function +must be optional: if the application does not supply it, the square root function +must still compute square roots.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Library, and can be reasonably +considered independent and separate works in themselves, then this License, +and its terms, do not apply to those sections when you distribute them as +separate works. But when you distribute the same sections as part of a whole +which is a work based on the Library, the distribution of the whole must be +on the terms of this License, whose permissions for other licensees extend +to the entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest your +rights to work written entirely by you; rather, the intent is to exercise +the right to control the distribution of derivative or collective works based +on the Library. + +In addition, mere aggregation of another work not based on the Library with +the Library (or with a work based on the Library) on a volume of a storage +or distribution medium does not bring the other work under the scope of this +License. + +3. You may opt to apply the terms of the ordinary GNU General Public License +instead of this License to a given copy of the Library. To do this, you must +alter all the notices that refer to this License, so that they refer to the +ordinary GNU General Public License, version 2, instead of to this License. +(If a newer version than version 2 of the ordinary GNU General Public License +has appeared, then you can specify that version instead if you wish.) Do not +make any other change in these notices. + +Once this change is made in a given copy, it is irreversible for that copy, +so the ordinary GNU General Public License applies to all subsequent copies +and derivative works made from that copy. + +This option is useful when you wish to copy part of the code of the Library +into a program that is not a library. + +4. You may copy and distribute the Library (or a portion or derivative of +it, under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you accompany it with the complete corresponding +machine-readable source code, which must be distributed under the terms of +Sections 1 and 2 above on a medium customarily used for software interchange. + +If distribution of object code is made by offering access to copy from a designated +place, then offering equivalent access to copy the source code from the same +place satisfies the requirement to distribute the source code, even though +third parties are not compelled to copy the source along with the object code. + +5. A program that contains no derivative of any portion of the Library, but +is designed to work with the Library by being compiled or linked with it, +is called a "work that uses the Library". Such a work, in isolation, is not +a derivative work of the Library, and therefore falls outside the scope of +this License. + +However, linking a "work that uses the Library" with the Library creates an +executable that is a derivative of the Library (because it contains portions +of the Library), rather than a "work that uses the library". The executable +is therefore covered by this License. Section 6 states terms for distribution +of such executables. + +When a "work that uses the Library" uses material from a header file that +is part of the Library, the object code for the work may be a derivative work +of the Library even though the source code is not. Whether this is true is +especially significant if the work can be linked without the Library, or if +the work is itself a library. The threshold for this to be true is not precisely +defined by law. + +If such an object file uses only numerical parameters, data structure layouts +and accessors, and small macros and small inline functions (ten lines or less +in length), then the use of the object file is unrestricted, regardless of +whether it is legally a derivative work. (Executables containing this object +code plus portions of the Library will still fall under Section 6.) + +Otherwise, if the work is a derivative of the Library, you may distribute +the object code for the work under the terms of Section 6. Any executables +containing that work also fall under Section 6, whether or not they are linked +directly with the Library itself. + +6. As an exception to the Sections above, you may also compile or link a "work +that uses the Library" with the Library to produce a work containing portions +of the Library, and distribute that work under terms of your choice, provided +that the terms permit modification of the work for the customer's own use +and reverse engineering for debugging such modifications. + +You must give prominent notice with each copy of the work that the Library +is used in it and that the Library and its use are covered by this License. +You must supply a copy of this License. If the work during execution displays +copyright notices, you must include the copyright notice for the Library among +them, as well as a reference directing the user to the copy of this License. +Also, you must do one of these things: + +a) Accompany the work with the complete corresponding machine-readable source +code for the Library including whatever changes were used in the work (which +must be distributed under Sections 1 and 2 above); and, if the work is an +executable linked with the Library, with the complete machine-readable "work +that uses the Library", as object code and/or source code, so that the user +can modify the Library and then relink to produce a modified executable containing +the modified Library. (It is understood that the user who changes the contents +of definitions files in the Library will not necessarily be able to recompile +the application to use the modified definitions.) + +b) Accompany the work with a written offer, valid for at least three years, +to give the same user the materials specified in Subsection 6a, above, for +a charge no more than the cost of performing this distribution. + +c) If distribution of the work is made by offering access to copy from a designated +place, offer equivalent access to copy the above specified materials from +the same place. + +d) Verify that the user has already received a copy of these materials or +that you have already sent this user a copy. + +For an executable, the required form of the "work that uses the Library" must +include any data and utility programs needed for reproducing the executable +from it. However, as a special exception, the source code distributed need +not include anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the operating +system on which the executable runs, unless that component itself accompanies +the executable. + +It may happen that this requirement contradicts the license restrictions of +other proprietary libraries that do not normally accompany the operating system. +Such a contradiction means you cannot use both them and the Library together +in an executable that you distribute. + +7. You may place library facilities that are a work based on the Library side-by-side +in a single library together with other library facilities not covered by +this License, and distribute such a combined library, provided that the separate +distribution of the work based on the Library and of the other library facilities +is otherwise permitted, and provided that you do these two things: + +a) Accompany the combined library with a copy of the same work based on the +Library, uncombined with any other library facilities. This must be distributed +under the terms of the Sections above. + +b) Give prominent notice with the combined library of the fact that part of +it is a work based on the Library, and explaining where to find the accompanying +uncombined form of the same work. + +8. You may not copy, modify, sublicense, link with, or distribute the Library +except as expressly provided under this License. Any attempt otherwise to +copy, modify, sublicense, link with, or distribute the Library is void, and +will automatically terminate your rights under this License. However, parties +who have received copies, or rights, from you under this License will not +have their licenses terminated so long as such parties remain in full compliance. + +9. You are not required to accept this License, since you have not signed +it. However, nothing else grants you permission to modify or distribute the +Library or its derivative works. These actions are prohibited by law if you +do not accept this License. Therefore, by modifying or distributing the Library +(or any work based on the Library), you indicate your acceptance of this License +to do so, and all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + +10. Each time you redistribute the Library (or any work based on the Library), +the recipient automatically receives a license from the original licensor +to copy, distribute, link with or modify the Library subject to these terms +and conditions. You may not impose any further restrictions on the recipients' +exercise of the rights granted herein. You are not responsible for enforcing +compliance by third parties to this License. + +11. If, as a consequence of a court judgment or allegation of patent infringement +or for any other reason (not limited to patent issues), conditions are imposed +on you (whether by court order, agreement or otherwise) that contradict the +conditions of this License, they do not excuse you from the conditions of +this License. If you cannot distribute so as to satisfy simultaneously your +obligations under this License and any other pertinent obligations, then as +a consequence you may not distribute the Library at all. For example, if a +patent license would not permit royalty-free redistribution of the Library +by all those who receive copies directly or indirectly through you, then the +only way you could satisfy both it and this License would be to refrain entirely +from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents +or other property right claims or to contest validity of any such claims; +this section has the sole purpose of protecting the integrity of the free +software distribution system which is implemented by public license practices. +Many people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose +that choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +12. If the distribution and/or use of the Library is restricted in certain +countries either by patents or by copyrighted interfaces, the original copyright +holder who places the Library under this License may add an explicit geographical +distribution limitation excluding those countries, so that distribution is +permitted only in or among countries not thus excluded. In such case, this +License incorporates the limitation as if written in the body of this License. + +13. The Free Software Foundation may publish revised and/or new versions of +the Library General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to address +new problems or concerns. + +Each version is given a distinguishing version number. If the Library specifies +a version number of this License which applies to it and "any later version", +you have the option of following the terms and conditions either of that version +or of any later version published by the Free Software Foundation. If the +Library does not specify a license version number, you may choose any version +ever published by the Free Software Foundation. + +14. If you wish to incorporate parts of the Library into other free programs +whose distribution conditions are incompatible with these, write to the author +to ask for permission. For software which is copyrighted by the Free Software +Foundation, write to the Free Software Foundation; we sometimes make exceptions +for this. Our decision will be guided by the two goals of preserving the free +status of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + +15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR +THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE +STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY +"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE +OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE +THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE +OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA +OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES +OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH +HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Libraries + +If you develop a new library, and you want it to be of the greatest possible +use to the public, we recommend making it free software that everyone can +redistribute and change. You can do so by permitting redistribution under +these terms (or, alternatively, under the terms of the ordinary General Public +License). + +To apply these terms, attach the following notices to the library. It is safest +to attach them to the start of each source file to most effectively convey +the exclusion of warranty; and each file should have at least the "copyright" +line and a pointer to where the full notice is found. + +one line to give the library's name and an idea of what it does. + +Copyright (C) year name of author + +This library is free software; you can redistribute it and/or modify it under +the terms of the GNU Library General Public License as published by the Free +Software Foundation; either version 2 of the License, or (at your option) +any later version. + +This library is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more +details. + +You should have received a copy of the GNU Library General Public License +along with this library; if not, write to the Free Software Foundation, Inc., +51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your school, +if any, to sign a "copyright disclaimer" for the library, if necessary. Here +is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in + +the library `Frob' (a library for tweaking knobs) written + +by James Random Hacker. + +signature of Ty Coon, 1 April 1990 + +Ty Coon, President of Vice + +That's all there is to it! diff --git a/LICENSES/LGPL-2.1-only.txt b/LICENSES/LGPL-2.1-only.txt new file mode 100644 index 00000000..130dffb3 --- /dev/null +++ b/LICENSES/LGPL-2.1-only.txt @@ -0,0 +1,467 @@ +GNU LESSER GENERAL PUBLIC LICENSE + +Version 2.1, February 1999 + +Copyright (C) 1991, 1999 Free Software Foundation, Inc. + +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts as the +successor of the GNU Library Public License, version 2, hence the version +number 2.1.] + +Preamble + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public Licenses are intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. + +This license, the Lesser General Public License, applies to some specially +designated software packages--typically libraries--of the Free Software Foundation +and other authors who decide to use it. You can use it too, but we suggest +you first think carefully about whether this license or the ordinary General +Public License is the better strategy to use in any particular case, based +on the explanations below. + +When we speak of free software, we are referring to freedom of use, not price. +Our General Public Licenses are designed to make sure that you have the freedom +to distribute copies of free software (and charge for this service if you +wish); that you receive source code or can get it if you want it; that you +can change the software and use pieces of it in new free programs; and that +you are informed that you can do these things. + +To protect your rights, we need to make restrictions that forbid distributors +to deny you these rights or to ask you to surrender these rights. These restrictions +translate to certain responsibilities for you if you distribute copies of +the library or if you modify it. + +For example, if you distribute copies of the library, whether gratis or for +a fee, you must give the recipients all the rights that we gave you. You must +make sure that they, too, receive or can get the source code. If you link +other code with the library, you must provide complete object files to the +recipients, so that they can relink them with the library after making changes +to the library and recompiling it. And you must show them these terms so they +know their rights. + +We protect your rights with a two-step method: (1) we copyright the library, +and (2) we offer you this license, which gives you legal permission to copy, +distribute and/or modify the library. + +To protect each distributor, we want to make it very clear that there is no +warranty for the free library. Also, if the library is modified by someone +else and passed on, the recipients should know that what they have is not +the original version, so that the original author's reputation will not be +affected by problems that might be introduced by others. + +Finally, software patents pose a constant threat to the existence of any free +program. We wish to make sure that a company cannot effectively restrict the +users of a free program by obtaining a restrictive license from a patent holder. +Therefore, we insist that any patent license obtained for a version of the +library must be consistent with the full freedom of use specified in this +license. + +Most GNU software, including some libraries, is covered by the ordinary GNU +General Public License. This license, the GNU Lesser General Public License, +applies to certain designated libraries, and is quite different from the ordinary +General Public License. We use this license for certain libraries in order +to permit linking those libraries into non-free programs. + +When a program is linked with a library, whether statically or using a shared +library, the combination of the two is legally speaking a combined work, a +derivative of the original library. The ordinary General Public License therefore +permits such linking only if the entire combination fits its criteria of freedom. +The Lesser General Public License permits more lax criteria for linking other +code with the library. + +We call this license the "Lesser" General Public License because it does Less +to protect the user's freedom than the ordinary General Public License. It +also provides other free software developers Less of an advantage over competing +non-free programs. These disadvantages are the reason we use the ordinary +General Public License for many libraries. However, the Lesser license provides +advantages in certain special circumstances. + +For example, on rare occasions, there may be a special need to encourage the +widest possible use of a certain library, so that it becomes a de-facto standard. +To achieve this, non-free programs must be allowed to use the library. A more +frequent case is that a free library does the same job as widely used non-free +libraries. In this case, there is little to gain by limiting the free library +to free software only, so we use the Lesser General Public License. + +In other cases, permission to use a particular library in non-free programs +enables a greater number of people to use a large body of free software. For +example, permission to use the GNU C Library in non-free programs enables +many more people to use the whole GNU operating system, as well as its variant, +the GNU/Linux operating system. + +Although the Lesser General Public License is Less protective of the users' +freedom, it does ensure that the user of a program that is linked with the +Library has the freedom and the wherewithal to run that program using a modified +version of the Library. + +The precise terms and conditions for copying, distribution and modification +follow. Pay close attention to the difference between a "work based on the +library" and a "work that uses the library". The former contains code derived +from the library, whereas the latter must be combined with the library in +order to run. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License Agreement applies to any software library or other program +which contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Lesser General +Public License (also called "this License"). Each licensee is addressed as +"you". + +A "library" means a collection of software functions and/or data prepared +so as to be conveniently linked with application programs (which use some +of those functions and data) to form executables. + +The "Library", below, refers to any such software library or work which has +been distributed under these terms. A "work based on the Library" means either +the Library or any derivative work under copyright law: that is to say, a +work containing the Library or a portion of it, either verbatim or with modifications +and/or translated straightforwardly into another language. (Hereinafter, translation +is included without limitation in the term "modification".) + +"Source code" for a work means the preferred form of the work for making modifications +to it. For a library, complete source code means all the source code for all +modules it contains, plus any associated interface definition files, plus +the scripts used to control compilation and installation of the library. + +Activities other than copying, distribution and modification are not covered +by this License; they are outside its scope. The act of running a program +using the Library is not restricted, and output from such a program is covered +only if its contents constitute a work based on the Library (independent of +the use of the Library in a tool for writing it). Whether that is true depends +on what the Library does and what the program that uses the Library does. + +1. You may copy and distribute verbatim copies of the Library's complete source +code as you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and disclaimer +of warranty; keep intact all the notices that refer to this License and to +the absence of any warranty; and distribute a copy of this License along with +the Library. + +You may charge a fee for the physical act of transferring a copy, and you +may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Library or any portion of it, +thus forming a work based on the Library, and copy and distribute such modifications +or work under the terms of Section 1 above, provided that you also meet all +of these conditions: + + a) The modified work must itself be a software library. + +b) You must cause the files modified to carry prominent notices stating that +you changed the files and the date of any change. + +c) You must cause the whole of the work to be licensed at no charge to all +third parties under the terms of this License. + +d) If a facility in the modified Library refers to a function or a table of +data to be supplied by an application program that uses the facility, other +than as an argument passed when the facility is invoked, then you must make +a good faith effort to ensure that, in the event an application does not supply +such function or table, the facility still operates, and performs whatever +part of its purpose remains meaningful. + +(For example, a function in a library to compute square roots has a purpose +that is entirely well-defined independent of the application. Therefore, Subsection +2d requires that any application-supplied function or table used by this function +must be optional: if the application does not supply it, the square root function +must still compute square roots.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Library, and can be reasonably +considered independent and separate works in themselves, then this License, +and its terms, do not apply to those sections when you distribute them as +separate works. But when you distribute the same sections as part of a whole +which is a work based on the Library, the distribution of the whole must be +on the terms of this License, whose permissions for other licensees extend +to the entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest your +rights to work written entirely by you; rather, the intent is to exercise +the right to control the distribution of derivative or collective works based +on the Library. + +In addition, mere aggregation of another work not based on the Library with +the Library (or with a work based on the Library) on a volume of a storage +or distribution medium does not bring the other work under the scope of this +License. + +3. You may opt to apply the terms of the ordinary GNU General Public License +instead of this License to a given copy of the Library. To do this, you must +alter all the notices that refer to this License, so that they refer to the +ordinary GNU General Public License, version 2, instead of to this License. +(If a newer version than version 2 of the ordinary GNU General Public License +has appeared, then you can specify that version instead if you wish.) Do not +make any other change in these notices. + +Once this change is made in a given copy, it is irreversible for that copy, +so the ordinary GNU General Public License applies to all subsequent copies +and derivative works made from that copy. + +This option is useful when you wish to copy part of the code of the Library +into a program that is not a library. + +4. You may copy and distribute the Library (or a portion or derivative of +it, under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you accompany it with the complete corresponding +machine-readable source code, which must be distributed under the terms of +Sections 1 and 2 above on a medium customarily used for software interchange. + +If distribution of object code is made by offering access to copy from a designated +place, then offering equivalent access to copy the source code from the same +place satisfies the requirement to distribute the source code, even though +third parties are not compelled to copy the source along with the object code. + +5. A program that contains no derivative of any portion of the Library, but +is designed to work with the Library by being compiled or linked with it, +is called a "work that uses the Library". Such a work, in isolation, is not +a derivative work of the Library, and therefore falls outside the scope of +this License. + +However, linking a "work that uses the Library" with the Library creates an +executable that is a derivative of the Library (because it contains portions +of the Library), rather than a "work that uses the library". The executable +is therefore covered by this License. Section 6 states terms for distribution +of such executables. + +When a "work that uses the Library" uses material from a header file that +is part of the Library, the object code for the work may be a derivative work +of the Library even though the source code is not. Whether this is true is +especially significant if the work can be linked without the Library, or if +the work is itself a library. The threshold for this to be true is not precisely +defined by law. + +If such an object file uses only numerical parameters, data structure layouts +and accessors, and small macros and small inline functions (ten lines or less +in length), then the use of the object file is unrestricted, regardless of +whether it is legally a derivative work. (Executables containing this object +code plus portions of the Library will still fall under Section 6.) + +Otherwise, if the work is a derivative of the Library, you may distribute +the object code for the work under the terms of Section 6. Any executables +containing that work also fall under Section 6, whether or not they are linked +directly with the Library itself. + +6. As an exception to the Sections above, you may also combine or link a "work +that uses the Library" with the Library to produce a work containing portions +of the Library, and distribute that work under terms of your choice, provided +that the terms permit modification of the work for the customer's own use +and reverse engineering for debugging such modifications. + +You must give prominent notice with each copy of the work that the Library +is used in it and that the Library and its use are covered by this License. +You must supply a copy of this License. If the work during execution displays +copyright notices, you must include the copyright notice for the Library among +them, as well as a reference directing the user to the copy of this License. +Also, you must do one of these things: + +a) Accompany the work with the complete corresponding machine-readable source +code for the Library including whatever changes were used in the work (which +must be distributed under Sections 1 and 2 above); and, if the work is an +executable linked with the Library, with the complete machine-readable "work +that uses the Library", as object code and/or source code, so that the user +can modify the Library and then relink to produce a modified executable containing +the modified Library. (It is understood that the user who changes the contents +of definitions files in the Library will not necessarily be able to recompile +the application to use the modified definitions.) + +b) Use a suitable shared library mechanism for linking with the Library. A +suitable mechanism is one that (1) uses at run time a copy of the library +already present on the user's computer system, rather than copying library +functions into the executable, and (2) will operate properly with a modified +version of the library, if the user installs one, as long as the modified +version is interface-compatible with the version that the work was made with. + +c) Accompany the work with a written offer, valid for at least three years, +to give the same user the materials specified in Subsection 6a, above, for +a charge no more than the cost of performing this distribution. + +d) If distribution of the work is made by offering access to copy from a designated +place, offer equivalent access to copy the above specified materials from +the same place. + +e) Verify that the user has already received a copy of these materials or +that you have already sent this user a copy. + +For an executable, the required form of the "work that uses the Library" must +include any data and utility programs needed for reproducing the executable +from it. However, as a special exception, the materials to be distributed +need not include anything that is normally distributed (in either source or +binary form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component itself +accompanies the executable. + +It may happen that this requirement contradicts the license restrictions of +other proprietary libraries that do not normally accompany the operating system. +Such a contradiction means you cannot use both them and the Library together +in an executable that you distribute. + +7. You may place library facilities that are a work based on the Library side-by-side +in a single library together with other library facilities not covered by +this License, and distribute such a combined library, provided that the separate +distribution of the work based on the Library and of the other library facilities +is otherwise permitted, and provided that you do these two things: + +a) Accompany the combined library with a copy of the same work based on the +Library, uncombined with any other library facilities. This must be distributed +under the terms of the Sections above. + +b) Give prominent notice with the combined library of the fact that part of +it is a work based on the Library, and explaining where to find the accompanying +uncombined form of the same work. + +8. You may not copy, modify, sublicense, link with, or distribute the Library +except as expressly provided under this License. Any attempt otherwise to +copy, modify, sublicense, link with, or distribute the Library is void, and +will automatically terminate your rights under this License. However, parties +who have received copies, or rights, from you under this License will not +have their licenses terminated so long as such parties remain in full compliance. + +9. You are not required to accept this License, since you have not signed +it. However, nothing else grants you permission to modify or distribute the +Library or its derivative works. These actions are prohibited by law if you +do not accept this License. Therefore, by modifying or distributing the Library +(or any work based on the Library), you indicate your acceptance of this License +to do so, and all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + +10. Each time you redistribute the Library (or any work based on the Library), +the recipient automatically receives a license from the original licensor +to copy, distribute, link with or modify the Library subject to these terms +and conditions. You may not impose any further restrictions on the recipients' +exercise of the rights granted herein. You are not responsible for enforcing +compliance by third parties with this License. + +11. If, as a consequence of a court judgment or allegation of patent infringement +or for any other reason (not limited to patent issues), conditions are imposed +on you (whether by court order, agreement or otherwise) that contradict the +conditions of this License, they do not excuse you from the conditions of +this License. If you cannot distribute so as to satisfy simultaneously your +obligations under this License and any other pertinent obligations, then as +a consequence you may not distribute the Library at all. For example, if a +patent license would not permit royalty-free redistribution of the Library +by all those who receive copies directly or indirectly through you, then the +only way you could satisfy both it and this License would be to refrain entirely +from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents +or other property right claims or to contest validity of any such claims; +this section has the sole purpose of protecting the integrity of the free +software distribution system which is implemented by public license practices. +Many people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose +that choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +12. If the distribution and/or use of the Library is restricted in certain +countries either by patents or by copyrighted interfaces, the original copyright +holder who places the Library under this License may add an explicit geographical +distribution limitation excluding those countries, so that distribution is +permitted only in or among countries not thus excluded. In such case, this +License incorporates the limitation as if written in the body of this License. + +13. The Free Software Foundation may publish revised and/or new versions of +the Lesser General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to address +new problems or concerns. + +Each version is given a distinguishing version number. If the Library specifies +a version number of this License which applies to it and "any later version", +you have the option of following the terms and conditions either of that version +or of any later version published by the Free Software Foundation. If the +Library does not specify a license version number, you may choose any version +ever published by the Free Software Foundation. + +14. If you wish to incorporate parts of the Library into other free programs +whose distribution conditions are incompatible with these, write to the author +to ask for permission. For software which is copyrighted by the Free Software +Foundation, write to the Free Software Foundation; we sometimes make exceptions +for this. Our decision will be guided by the two goals of preserving the free +status of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + +15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR +THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE +STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY +"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE +OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE +THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE +OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA +OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES +OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH +HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Libraries + +If you develop a new library, and you want it to be of the greatest possible +use to the public, we recommend making it free software that everyone can +redistribute and change. You can do so by permitting redistribution under +these terms (or, alternatively, under the terms of the ordinary General Public +License). + +To apply these terms, attach the following notices to the library. It is safest +to attach them to the start of each source file to most effectively convey +the exclusion of warranty; and each file should have at least the "copyright" +line and a pointer to where the full notice is found. + +< one line to give the library's name and an idea of what it does. > + +Copyright (C) < year > < name of author > + +This library is free software; you can redistribute it and/or modify it under +the terms of the GNU Lesser General Public License as published by the Free +Software Foundation; either version 2.1 of the License, or (at your option) +any later version. + +This library is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. + +You should have received a copy of the GNU Lesser General Public License along +with this library; if not, write to the Free Software Foundation, Inc., 51 +Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information +on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your school, +if any, to sign a "copyright disclaimer" for the library, if necessary. Here +is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in + +the library `Frob' (a library for tweaking knobs) written + +by James Random Hacker. + +< signature of Ty Coon > , 1 April 1990 + +Ty Coon, President of Vice + +That's all there is to it! diff --git a/LICENSES/LGPL-2.1-or-later.txt b/LICENSES/LGPL-2.1-or-later.txt new file mode 100644 index 00000000..04bb156e --- /dev/null +++ b/LICENSES/LGPL-2.1-or-later.txt @@ -0,0 +1,468 @@ +GNU LESSER GENERAL PUBLIC LICENSE + +Version 2.1, February 1999 + +Copyright (C) 1991, 1999 Free Software Foundation, Inc. + +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts as the +successor of the GNU Library Public License, version 2, hence the version +number 2.1.] + +Preamble + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public Licenses are intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. + +This license, the Lesser General Public License, applies to some specially +designated software packages--typically libraries--of the Free Software Foundation +and other authors who decide to use it. You can use it too, but we suggest +you first think carefully about whether this license or the ordinary General +Public License is the better strategy to use in any particular case, based +on the explanations below. + +When we speak of free software, we are referring to freedom of use, not price. +Our General Public Licenses are designed to make sure that you have the freedom +to distribute copies of free software (and charge for this service if you +wish); that you receive source code or can get it if you want it; that you +can change the software and use pieces of it in new free programs; and that +you are informed that you can do these things. + +To protect your rights, we need to make restrictions that forbid distributors +to deny you these rights or to ask you to surrender these rights. These restrictions +translate to certain responsibilities for you if you distribute copies of +the library or if you modify it. + +For example, if you distribute copies of the library, whether gratis or for +a fee, you must give the recipients all the rights that we gave you. You must +make sure that they, too, receive or can get the source code. If you link +other code with the library, you must provide complete object files to the +recipients, so that they can relink them with the library after making changes +to the library and recompiling it. And you must show them these terms so they +know their rights. + +We protect your rights with a two-step method: (1) we copyright the library, +and (2) we offer you this license, which gives you legal permission to copy, +distribute and/or modify the library. + +To protect each distributor, we want to make it very clear that there is no +warranty for the free library. Also, if the library is modified by someone +else and passed on, the recipients should know that what they have is not +the original version, so that the original author's reputation will not be +affected by problems that might be introduced by others. + +Finally, software patents pose a constant threat to the existence of any free +program. We wish to make sure that a company cannot effectively restrict the +users of a free program by obtaining a restrictive license from a patent holder. +Therefore, we insist that any patent license obtained for a version of the +library must be consistent with the full freedom of use specified in this +license. + +Most GNU software, including some libraries, is covered by the ordinary GNU +General Public License. This license, the GNU Lesser General Public License, +applies to certain designated libraries, and is quite different from the ordinary +General Public License. We use this license for certain libraries in order +to permit linking those libraries into non-free programs. + +When a program is linked with a library, whether statically or using a shared +library, the combination of the two is legally speaking a combined work, a +derivative of the original library. The ordinary General Public License therefore +permits such linking only if the entire combination fits its criteria of freedom. +The Lesser General Public License permits more lax criteria for linking other +code with the library. + +We call this license the "Lesser" General Public License because it does Less +to protect the user's freedom than the ordinary General Public License. It +also provides other free software developers Less of an advantage over competing +non-free programs. These disadvantages are the reason we use the ordinary +General Public License for many libraries. However, the Lesser license provides +advantages in certain special circumstances. + +For example, on rare occasions, there may be a special need to encourage the +widest possible use of a certain library, so that it becomes a de-facto standard. +To achieve this, non-free programs must be allowed to use the library. A more +frequent case is that a free library does the same job as widely used non-free +libraries. In this case, there is little to gain by limiting the free library +to free software only, so we use the Lesser General Public License. + +In other cases, permission to use a particular library in non-free programs +enables a greater number of people to use a large body of free software. For +example, permission to use the GNU C Library in non-free programs enables +many more people to use the whole GNU operating system, as well as its variant, +the GNU/Linux operating system. + +Although the Lesser General Public License is Less protective of the users' +freedom, it does ensure that the user of a program that is linked with the +Library has the freedom and the wherewithal to run that program using a modified +version of the Library. + +The precise terms and conditions for copying, distribution and modification +follow. Pay close attention to the difference between a "work based on the +library" and a "work that uses the library". The former contains code derived +from the library, whereas the latter must be combined with the library in +order to run. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License Agreement applies to any software library or other program +which contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Lesser General +Public License (also called "this License"). Each licensee is addressed as +"you". + +A "library" means a collection of software functions and/or data prepared +so as to be conveniently linked with application programs (which use some +of those functions and data) to form executables. + +The "Library", below, refers to any such software library or work which has +been distributed under these terms. A "work based on the Library" means either +the Library or any derivative work under copyright law: that is to say, a +work containing the Library or a portion of it, either verbatim or with modifications +and/or translated straightforwardly into another language. (Hereinafter, translation +is included without limitation in the term "modification".) + +"Source code" for a work means the preferred form of the work for making modifications +to it. For a library, complete source code means all the source code for all +modules it contains, plus any associated interface definition files, plus +the scripts used to control compilation and installation of the library. + +Activities other than copying, distribution and modification are not covered +by this License; they are outside its scope. The act of running a program +using the Library is not restricted, and output from such a program is covered +only if its contents constitute a work based on the Library (independent of +the use of the Library in a tool for writing it). Whether that is true depends +on what the Library does and what the program that uses the Library does. + +1. You may copy and distribute verbatim copies of the Library's complete source +code as you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and disclaimer +of warranty; keep intact all the notices that refer to this License and to +the absence of any warranty; and distribute a copy of this License along with +the Library. + +You may charge a fee for the physical act of transferring a copy, and you +may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Library or any portion of it, +thus forming a work based on the Library, and copy and distribute such modifications +or work under the terms of Section 1 above, provided that you also meet all +of these conditions: + + a) The modified work must itself be a software library. + +b) You must cause the files modified to carry prominent notices stating that +you changed the files and the date of any change. + +c) You must cause the whole of the work to be licensed at no charge to all +third parties under the terms of this License. + +d) If a facility in the modified Library refers to a function or a table of +data to be supplied by an application program that uses the facility, other +than as an argument passed when the facility is invoked, then you must make +a good faith effort to ensure that, in the event an application does not supply +such function or table, the facility still operates, and performs whatever +part of its purpose remains meaningful. + +(For example, a function in a library to compute square roots has a purpose +that is entirely well-defined independent of the application. Therefore, Subsection +2d requires that any application-supplied function or table used by this function +must be optional: if the application does not supply it, the square root function +must still compute square roots.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Library, and can be reasonably +considered independent and separate works in themselves, then this License, +and its terms, do not apply to those sections when you distribute them as +separate works. But when you distribute the same sections as part of a whole +which is a work based on the Library, the distribution of the whole must be +on the terms of this License, whose permissions for other licensees extend +to the entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest your +rights to work written entirely by you; rather, the intent is to exercise +the right to control the distribution of derivative or collective works based +on the Library. + +In addition, mere aggregation of another work not based on the Library with +the Library (or with a work based on the Library) on a volume of a storage +or distribution medium does not bring the other work under the scope of this +License. + +3. You may opt to apply the terms of the ordinary GNU General Public License +instead of this License to a given copy of the Library. To do this, you must +alter all the notices that refer to this License, so that they refer to the +ordinary GNU General Public License, version 2, instead of to this License. +(If a newer version than version 2 of the ordinary GNU General Public License +has appeared, then you can specify that version instead if you wish.) Do not +make any other change in these notices. + +Once this change is made in a given copy, it is irreversible for that copy, +so the ordinary GNU General Public License applies to all subsequent copies +and derivative works made from that copy. + +This option is useful when you wish to copy part of the code of the Library +into a program that is not a library. + +4. You may copy and distribute the Library (or a portion or derivative of +it, under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you accompany it with the complete corresponding +machine-readable source code, which must be distributed under the terms of +Sections 1 and 2 above on a medium customarily used for software interchange. + +If distribution of object code is made by offering access to copy from a designated +place, then offering equivalent access to copy the source code from the same +place satisfies the requirement to distribute the source code, even though +third parties are not compelled to copy the source along with the object code. + +5. A program that contains no derivative of any portion of the Library, but +is designed to work with the Library by being compiled or linked with it, +is called a "work that uses the Library". Such a work, in isolation, is not +a derivative work of the Library, and therefore falls outside the scope of +this License. + +However, linking a "work that uses the Library" with the Library creates an +executable that is a derivative of the Library (because it contains portions +of the Library), rather than a "work that uses the library". The executable +is therefore covered by this License. Section 6 states terms for distribution +of such executables. + +When a "work that uses the Library" uses material from a header file that +is part of the Library, the object code for the work may be a derivative work +of the Library even though the source code is not. Whether this is true is +especially significant if the work can be linked without the Library, or if +the work is itself a library. The threshold for this to be true is not precisely +defined by law. + +If such an object file uses only numerical parameters, data structure layouts +and accessors, and small macros and small inline functions (ten lines or less +in length), then the use of the object file is unrestricted, regardless of +whether it is legally a derivative work. (Executables containing this object +code plus portions of the Library will still fall under Section 6.) + +Otherwise, if the work is a derivative of the Library, you may distribute +the object code for the work under the terms of Section 6. Any executables +containing that work also fall under Section 6, whether or not they are linked +directly with the Library itself. + +6. As an exception to the Sections above, you may also combine or link a "work +that uses the Library" with the Library to produce a work containing portions +of the Library, and distribute that work under terms of your choice, provided +that the terms permit modification of the work for the customer's own use +and reverse engineering for debugging such modifications. + +You must give prominent notice with each copy of the work that the Library +is used in it and that the Library and its use are covered by this License. +You must supply a copy of this License. If the work during execution displays +copyright notices, you must include the copyright notice for the Library among +them, as well as a reference directing the user to the copy of this License. +Also, you must do one of these things: + +a) Accompany the work with the complete corresponding machine-readable source +code for the Library including whatever changes were used in the work (which +must be distributed under Sections 1 and 2 above); and, if the work is an +executable linked with the Library, with the complete machine-readable "work +that uses the Library", as object code and/or source code, so that the user +can modify the Library and then relink to produce a modified executable containing +the modified Library. (It is understood that the user who changes the contents +of definitions files in the Library will not necessarily be able to recompile +the application to use the modified definitions.) + +b) Use a suitable shared library mechanism for linking with the Library. A +suitable mechanism is one that (1) uses at run time a copy of the library +already present on the user's computer system, rather than copying library +functions into the executable, and (2) will operate properly with a modified +version of the library, if the user installs one, as long as the modified +version is interface-compatible with the version that the work was made with. + +c) Accompany the work with a written offer, valid for at least three years, +to give the same user the materials specified in Subsection 6a, above, for +a charge no more than the cost of performing this distribution. + +d) If distribution of the work is made by offering access to copy from a designated +place, offer equivalent access to copy the above specified materials from +the same place. + +e) Verify that the user has already received a copy of these materials or +that you have already sent this user a copy. + +For an executable, the required form of the "work that uses the Library" must +include any data and utility programs needed for reproducing the executable +from it. However, as a special exception, the materials to be distributed +need not include anything that is normally distributed (in either source or +binary form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component itself +accompanies the executable. + +It may happen that this requirement contradicts the license restrictions of +other proprietary libraries that do not normally accompany the operating system. +Such a contradiction means you cannot use both them and the Library together +in an executable that you distribute. + +7. You may place library facilities that are a work based on the Library side-by-side +in a single library together with other library facilities not covered by +this License, and distribute such a combined library, provided that the separate +distribution of the work based on the Library and of the other library facilities +is otherwise permitted, and provided that you do these two things: + +a) Accompany the combined library with a copy of the same work based on the +Library, uncombined with any other library facilities. This must be distributed +under the terms of the Sections above. + +b) Give prominent notice with the combined library of the fact that part of +it is a work based on the Library, and explaining where to find the accompanying +uncombined form of the same work. + +8. You may not copy, modify, sublicense, link with, or distribute the Library +except as expressly provided under this License. Any attempt otherwise to +copy, modify, sublicense, link with, or distribute the Library is void, and +will automatically terminate your rights under this License. However, parties +who have received copies, or rights, from you under this License will not +have their licenses terminated so long as such parties remain in full compliance. + +9. You are not required to accept this License, since you have not signed +it. However, nothing else grants you permission to modify or distribute the +Library or its derivative works. These actions are prohibited by law if you +do not accept this License. Therefore, by modifying or distributing the Library +(or any work based on the Library), you indicate your acceptance of this License +to do so, and all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + +10. Each time you redistribute the Library (or any work based on the Library), +the recipient automatically receives a license from the original licensor +to copy, distribute, link with or modify the Library subject to these terms +and conditions. You may not impose any further restrictions on the recipients' +exercise of the rights granted herein. You are not responsible for enforcing +compliance by third parties with this License. + +11. If, as a consequence of a court judgment or allegation of patent infringement +or for any other reason (not limited to patent issues), conditions are imposed +on you (whether by court order, agreement or otherwise) that contradict the +conditions of this License, they do not excuse you from the conditions of +this License. If you cannot distribute so as to satisfy simultaneously your +obligations under this License and any other pertinent obligations, then as +a consequence you may not distribute the Library at all. For example, if a +patent license would not permit royalty-free redistribution of the Library +by all those who receive copies directly or indirectly through you, then the +only way you could satisfy both it and this License would be to refrain entirely +from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents +or other property right claims or to contest validity of any such claims; +this section has the sole purpose of protecting the integrity of the free +software distribution system which is implemented by public license practices. +Many people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose +that choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +12. If the distribution and/or use of the Library is restricted in certain +countries either by patents or by copyrighted interfaces, the original copyright +holder who places the Library under this License may add an explicit geographical +distribution limitation excluding those countries, so that distribution is +permitted only in or among countries not thus excluded. In such case, this +License incorporates the limitation as if written in the body of this License. + +13. The Free Software Foundation may publish revised and/or new versions of +the Lesser General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to address +new problems or concerns. + +Each version is given a distinguishing version number. If the Library specifies +a version number of this License which applies to it and "any later version", +you have the option of following the terms and conditions either of that version +or of any later version published by the Free Software Foundation. If the +Library does not specify a license version number, you may choose any version +ever published by the Free Software Foundation. + +14. If you wish to incorporate parts of the Library into other free programs +whose distribution conditions are incompatible with these, write to the author +to ask for permission. For software which is copyrighted by the Free Software +Foundation, write to the Free Software Foundation; we sometimes make exceptions +for this. Our decision will be guided by the two goals of preserving the free +status of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + +15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR +THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE +STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY +"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE +OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE +THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE +OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA +OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES +OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH +HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Libraries + +If you develop a new library, and you want it to be of the greatest possible +use to the public, we recommend making it free software that everyone can +redistribute and change. You can do so by permitting redistribution under +these terms (or, alternatively, under the terms of the ordinary General Public +License). + +To apply these terms, attach the following notices to the library. It is safest +to attach them to the start of each source file to most effectively convey +the exclusion of warranty; and each file should have at least the "copyright" +line and a pointer to where the full notice is found. + + + +Copyright (C) + +This library is free software; you can redistribute it and/or modify it under +the terms of the GNU Lesser General Public License as published by the Free +Software Foundation; either version 2.1 of the License, or (at your option) +any later version. + +This library is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. + +You should have received a copy of the GNU Lesser General Public License along +with this library; if not, write to the Free Software Foundation, Inc., 51 +Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your school, +if any, to sign a "copyright disclaimer" for the library, if necessary. Here +is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in + +the library `Frob' (a library for tweaking knobs) written + +by James Random Hacker. + +< signature of Ty Coon > , 1 April 1990 + +Ty Coon, President of Vice + +That's all there is to it! diff --git a/LICENSES/LGPL-3.0-only.txt b/LICENSES/LGPL-3.0-only.txt new file mode 100644 index 00000000..bd405afb --- /dev/null +++ b/LICENSES/LGPL-3.0-only.txt @@ -0,0 +1,163 @@ +GNU LESSER GENERAL PUBLIC LICENSE + +Version 3, 29 June 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +This version of the GNU Lesser General Public License incorporates the terms +and conditions of version 3 of the GNU General Public License, supplemented +by the additional permissions listed below. + + 0. Additional Definitions. + + + +As used herein, "this License" refers to version 3 of the GNU Lesser General +Public License, and the "GNU GPL" refers to version 3 of the GNU General Public +License. + + + +"The Library" refers to a covered work governed by this License, other than +an Application or a Combined Work as defined below. + + + +An "Application" is any work that makes use of an interface provided by the +Library, but which is not otherwise based on the Library. Defining a subclass +of a class defined by the Library is deemed a mode of using an interface provided +by the Library. + + + +A "Combined Work" is a work produced by combining or linking an Application +with the Library. The particular version of the Library with which the Combined +Work was made is also called the "Linked Version". + + + +The "Minimal Corresponding Source" for a Combined Work means the Corresponding +Source for the Combined Work, excluding any source code for portions of the +Combined Work that, considered in isolation, are based on the Application, +and not on the Linked Version. + + + +The "Corresponding Application Code" for a Combined Work means the object +code and/or source code for the Application, including any data and utility +programs needed for reproducing the Combined Work from the Application, but +excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + +You may convey a covered work under sections 3 and 4 of this License without +being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + +If you modify a copy of the Library, and, in your modifications, a facility +refers to a function or data to be supplied by an Application that uses the +facility (other than as an argument passed when the facility is invoked), +then you may convey a copy of the modified version: + +a) under this License, provided that you make a good faith effort to ensure +that, in the event an Application does not supply the function or data, the +facility still operates, and performs whatever part of its purpose remains +meaningful, or + +b) under the GNU GPL, with none of the additional permissions of this License +applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + +The object code form of an Application may incorporate material from a header +file that is part of the Library. You may convey such object code under terms +of your choice, provided that, if the incorporated material is not limited +to numerical parameters, data structure layouts and accessors, or small macros, +inline functions and templates (ten or fewer lines in length), you do both +of the following: + +a) Give prominent notice with each copy of the object code that the Library +is used in it and that the Library and its use are covered by this License. + +b) Accompany the object code with a copy of the GNU GPL and this license document. + + 4. Combined Works. + +You may convey a Combined Work under terms of your choice that, taken together, +effectively do not restrict modification of the portions of the Library contained +in the Combined Work and reverse engineering for debugging such modifications, +if you also do each of the following: + +a) Give prominent notice with each copy of the Combined Work that the Library +is used in it and that the Library and its use are covered by this License. + +b) Accompany the Combined Work with a copy of the GNU GPL and this license +document. + +c) For a Combined Work that displays copyright notices during execution, include +the copyright notice for the Library among these notices, as well as a reference +directing the user to the copies of the GNU GPL and this license document. + + d) Do one of the following: + +0) Convey the Minimal Corresponding Source under the terms of this License, +and the Corresponding Application Code in a form suitable for, and under terms +that permit, the user to recombine or relink the Application with a modified +version of the Linked Version to produce a modified Combined Work, in the +manner specified by section 6 of the GNU GPL for conveying Corresponding Source. + +1) Use a suitable shared library mechanism for linking with the Library. A +suitable mechanism is one that (a) uses at run time a copy of the Library +already present on the user's computer system, and (b) will operate properly +with a modified version of the Library that is interface-compatible with the +Linked Version. + +e) Provide Installation Information, but only if you would otherwise be required +to provide such information under section 6 of the GNU GPL, and only to the +extent that such information is necessary to install and execute a modified +version of the Combined Work produced by recombining or relinking the Application +with a modified version of the Linked Version. (If you use option 4d0, the +Installation Information must accompany the Minimal Corresponding Source and +Corresponding Application Code. If you use option 4d1, you must provide the +Installation Information in the manner specified by section 6 of the GNU GPL +for conveying Corresponding Source.) + + 5. Combined Libraries. + +You may place library facilities that are a work based on the Library side +by side in a single library together with other library facilities that are +not Applications and are not covered by this License, and convey such a combined +library under terms of your choice, if you do both of the following: + +a) Accompany the combined library with a copy of the same work based on the +Library, uncombined with any other library facilities, conveyed under the +terms of this License. + +b) Give prominent notice with the combined library that part of it is a work +based on the Library, and explaining where to find the accompanying uncombined +form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + +The Free Software Foundation may publish revised and/or new versions of the +GNU Lesser General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to address +new problems or concerns. + +Each version is given a distinguishing version number. If the Library as you +received it specifies that a certain numbered version of the GNU Lesser General +Public License "or any later version" applies to it, you have the option of +following the terms and conditions either of that published version or of +any later version published by the Free Software Foundation. If the Library +as you received it does not specify a version number of the GNU Lesser General +Public License, you may choose any version of the GNU Lesser General Public +License ever published by the Free Software Foundation. + +If the Library as you received it specifies that a proxy can decide whether +future versions of the GNU Lesser General Public License shall apply, that +proxy's public statement of acceptance of any version is permanent authorization +for you to choose that version for the Library. diff --git a/LICENSES/LicenseRef-KDE-Accepted-GPL.txt b/LICENSES/LicenseRef-KDE-Accepted-GPL.txt new file mode 100644 index 00000000..60a2dffc --- /dev/null +++ b/LICENSES/LicenseRef-KDE-Accepted-GPL.txt @@ -0,0 +1,12 @@ +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 3 of +the license or (at your option) at any later version that is +accepted by the membership of KDE e.V. (or its successor +approved by the membership of KDE e.V.), which shall act as a +proxy as defined in Section 14 of version 3 of the license. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. diff --git a/LICENSES/LicenseRef-KDE-Accepted-LGPL.txt b/LICENSES/LicenseRef-KDE-Accepted-LGPL.txt new file mode 100644 index 00000000..232b3c5d --- /dev/null +++ b/LICENSES/LicenseRef-KDE-Accepted-LGPL.txt @@ -0,0 +1,12 @@ +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 3 of the license or (at your option) any later version +that is accepted by the membership of KDE e.V. (or its successor +approved by the membership of KDE e.V.), which shall act as a +proxy as defined in Section 6 of version 3 of the license. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. diff --git a/LICENSES/MIT.txt b/LICENSES/MIT.txt new file mode 100644 index 00000000..2071b23b --- /dev/null +++ b/LICENSES/MIT.txt @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Mainpage.dox b/Mainpage.dox new file mode 100644 index 00000000..3ca2343a --- /dev/null +++ b/Mainpage.dox @@ -0,0 +1,7 @@ +/** @mainpage Plasma Add-Ons + * + * This module contains all kinds of Plasma add-ons. + * + */ +// DOXYGEN_NAME=Plasma Add-Ons +// DOXYGEN_ENABLE=YES diff --git a/README.md b/README.md new file mode 100644 index 00000000..5e4455b9 --- /dev/null +++ b/README.md @@ -0,0 +1,60 @@ +# Plasma Add-ons + +All kind of add-ons to improve your Plasma experience. + + +## Applets +#### Various desktop-specific applets +- Binary Clock +- Calculator +- Color Picker +- Comic Strip +- Dictionary +- Disk Quota +- Fifteen Puzzle +- Fuzzy Clock +- Grouping Plasmoid +- Kate Sessions +- Lock Keys Status +- Application Dashboard +- Konsole Profiles +- Media Frame +- Sticky Note +- Quicklaunch +- Timer +- User Switcher +- Weather Report +- Web browser + +## Kwin Effects +- Cube + +## Window Switchers +- Compact +- Large Icons +- Cover Switch +- Flip Switch +- Sidebar + +## Plasma Calendar Plugins +- Alternate Calendar +- Astonomical Calendar + +## Runners +#### Runners are plugins for KRunner, the launcher system used throughout Plasma +- Special Characters +- Unit Converter +- Date and Time +- Dictionary +- Kate Sessions +- Konsole Profiles +- Spell Checker + +## Wallpaper Providers +- Picture of the Day +- Hunyango +- Haenau + + +# Reporting bugs +Please use [KDE's bugtracker](https://bugs.kde.org) and report for [product kdeplasma-addons](https://bugs.kde.org/enter_bug.cgi?product=kdeplasma-addons). \ No newline at end of file diff --git a/appiumtests/CMakeLists.txt b/appiumtests/CMakeLists.txt new file mode 100644 index 00000000..0be5187f --- /dev/null +++ b/appiumtests/CMakeLists.txt @@ -0,0 +1,36 @@ +# SPDX-License-Identifier: BSD-3-Clause +# SPDX-FileCopyrightText: 2022 Harald Sitter + +if(NOT Qt${QT_MAJOR_VERSION}_VERSION VERSION_GREATER_EQUAL "6.5.0") + # Before Qt 6.5 the AbstractButtons didn't trigger properly at all. + return() +endif() + +if(NOT BUILD_TESTING OR NOT CMAKE_SYSTEM_NAME MATCHES "Linux") + return() +endif() + +find_package(SeleniumWebDriverATSPI) +set_package_properties(SeleniumWebDriverATSPI PROPERTIES + DESCRIPTION "Server component for selenium tests using Linux accessibility infrastructure" + PURPOSE "Needed for GUI tests" + URL "https://invent.kde.org/sdk/selenium-webdriver-at-spi" + TYPE OPTIONAL +) +if(NOT SeleniumWebDriverATSPI_FOUND) + return() +endif() + +add_subdirectory(calendarplugins) + +add_test( + NAME calculatortest + COMMAND selenium-webdriver-at-spi-run ${CMAKE_CURRENT_SOURCE_DIR}/calculatortest.py --failfast +) +set_tests_properties(calculatortest PROPERTIES TIMEOUT 300 ENVIRONMENT "TEST_WITH_KWIN_WAYLAND=1") + +add_test( + NAME keyboardindicatortest + COMMAND selenium-webdriver-at-spi-run ${CMAKE_CURRENT_SOURCE_DIR}/keyboardindicatortest.py --failfast +) +set_tests_properties(keyboardindicatortest PROPERTIES TIMEOUT 300 ENVIRONMENT "TEST_WITH_KWIN_WAYLAND=0") diff --git a/appiumtests/calculatortest.py b/appiumtests/calculatortest.py new file mode 100755 index 00000000..d7f32eea --- /dev/null +++ b/appiumtests/calculatortest.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 + +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: 2016 Microsoft Corporation. All rights reserved. +# SPDX-FileCopyrightText: 2021-2022 Harald Sitter + +import unittest +from typing import Final + +from appium import webdriver +from appium.options.common.base import AppiumOptions +from appium.webdriver.common.appiumby import AppiumBy +from selenium.webdriver.support.ui import WebDriverWait + +WIDGET_ID: Final = "org.kde.plasma.calculator" + + +class SimpleCalculatorTests(unittest.TestCase): + + @classmethod + def setUpClass(cls): + options = AppiumOptions() + options.set_capability("app", f"plasmawindowed -p org.kde.plasma.nano {WIDGET_ID}") + options.set_capability("environ", { + "QT_FATAL_WARNINGS": "1", + "QT_LOGGING_RULES": "qt.accessibility.atspi.warning=false;kf.plasma.core.warning=false;kf.windowsystem.warning=false;kf.kirigami.platform.warning=false;kf.coreaddons.warning=false", + }) + options.set_capability("timeouts", {'implicit': 10000}) + cls.driver = webdriver.Remote(command_executor='http://127.0.0.1:4723', options=options) + + def setUp(self): + self.driver.find_element(by=AppiumBy.NAME, value="AC").click() + wait = WebDriverWait(self.driver, 5) + wait.until(lambda x: self.getresults() == '0') + + def tearDown(self): + if not self._outcome.result.wasSuccessful(): + self.driver.get_screenshot_as_file("failed_test_shot_{}.png".format(self.id())) + + @classmethod + def tearDownClass(self): + self.driver.quit() + + def getresults(self): + displaytext = self.driver.find_element(by='description', value="Result").text + return displaytext + + def test_initialize(self): + self.driver.find_element(by=AppiumBy.NAME, value="AC").click() + self.driver.find_element(by=AppiumBy.NAME, value="7").click() + self.assertEqual(self.getresults(), "7") + + def test_addition(self): + self.driver.find_element(by=AppiumBy.NAME, value="1").click() + self.driver.find_element(by=AppiumBy.NAME, value="+").click() + self.driver.find_element(by=AppiumBy.NAME, value="7").click() + self.driver.find_element(by=AppiumBy.NAME, value="=").click() + self.assertEqual(self.getresults(), "8") + + def test_combination(self): + self.driver.find_element(by=AppiumBy.NAME, value="7").click() + self.driver.find_element(by=AppiumBy.NAME, value="×").click() + self.driver.find_element(by=AppiumBy.NAME, value="9").click() + self.driver.find_element(by=AppiumBy.NAME, value="+").click() + self.driver.find_element(by=AppiumBy.NAME, value="1").click() + self.driver.find_element(by=AppiumBy.NAME, value="=").click() + self.driver.find_element(by=AppiumBy.NAME, value="÷").click() + self.driver.find_element(by=AppiumBy.NAME, value="8").click() + self.driver.find_element(by=AppiumBy.NAME, value="=").click() + self.assertEqual(self.getresults(), "8") + + def test_division(self): + self.driver.find_element(by=AppiumBy.NAME, value="8").click() + self.driver.find_element(by=AppiumBy.NAME, value="8").click() + self.driver.find_element(by=AppiumBy.NAME, value="÷").click() + self.driver.find_element(by=AppiumBy.NAME, value="1").click() + self.driver.find_element(by=AppiumBy.NAME, value="1").click() + self.driver.find_element(by=AppiumBy.NAME, value="=").click() + self.assertEqual(self.getresults(), "8") + + def test_multiplication(self): + self.driver.find_element(by=AppiumBy.NAME, value="9").click() + self.driver.find_element(by=AppiumBy.NAME, value="×").click() + self.driver.find_element(by=AppiumBy.NAME, value="8").click() + self.driver.find_element(by=AppiumBy.NAME, value="=").click() + self.assertEqual(self.getresults(), "72") + + def test_subtraction(self): + self.driver.find_element(by=AppiumBy.NAME, value="9").click() + self.driver.find_element(by=AppiumBy.NAME, value="-").click() + self.driver.find_element(by=AppiumBy.NAME, value="1").click() + self.driver.find_element(by=AppiumBy.NAME, value="=").click() + self.assertEqual(self.getresults(), "8") + + +if __name__ == '__main__': + unittest.main() diff --git a/appiumtests/calendarplugins/CMakeLists.txt b/appiumtests/calendarplugins/CMakeLists.txt new file mode 100644 index 00000000..e02d9964 --- /dev/null +++ b/appiumtests/calendarplugins/CMakeLists.txt @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: 2024 Fushan Wen +# SPDX-License-Identifier: BSD-3-Clause + +add_test( + NAME alternatecalendartest + COMMAND selenium-webdriver-at-spi-run ${CMAKE_CURRENT_SOURCE_DIR}/alternatecalendartest.py --failfast +) diff --git a/appiumtests/calendarplugins/alternatecalendartest.py b/appiumtests/calendarplugins/alternatecalendartest.py new file mode 100755 index 00000000..2de781de --- /dev/null +++ b/appiumtests/calendarplugins/alternatecalendartest.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 + +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: 2024 Fushan Wen + +import subprocess +import unittest +from typing import Final + +from appium import webdriver +from appium.options.common.base import AppiumOptions +from appium.webdriver.common.appiumby import AppiumBy +from selenium.common.exceptions import NoSuchElementException +from selenium.webdriver.remote.webelement import WebElement +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.ui import WebDriverWait + +WIDGET_ID: Final = "org.kde.plasma.digitalclock" + + +class AlternateCalendarTests(unittest.TestCase): + + driver: webdriver.Remote + + @classmethod + def setUpClass(cls) -> None: + options = AppiumOptions() + options.set_capability("app", f"plasmawindowed -p org.kde.plasma.desktop {WIDGET_ID}") + options.set_capability("timeouts", {'implicit': 10000}) + options.set_capability("environ", { + "LC_ALL": "en_US.UTF-8", + }) + cls.driver = webdriver.Remote(command_executor='http://127.0.0.1:4723', options=options) + + def setUp(self) -> None: + pass + + def tearDown(self) -> None: + if not self._outcome.result.wasSuccessful(): + self.driver.get_screenshot_as_file(f"failed_test_shot_alternatecalendartest_{self.id()}.png") + + @classmethod + def tearDownClass(cls) -> None: + """ + Make sure to terminate the driver again, lest it dangles. + """ + cls.driver.quit() + + def test_1_config_dialog_1_enable_plugin(self) -> None: + """ + Opens the config dialog and enables the plugin + """ + subprocess.check_call(["plasmawindowed", "--config"]) + wait = WebDriverWait(self.driver, 10) + wait.until(EC.presence_of_element_located((AppiumBy.NAME, "Date format:"))) + + self.driver.find_element(AppiumBy.NAME, "Calendar").click() + wait = WebDriverWait(self.driver, 10) + wait.until(EC.presence_of_element_located((AppiumBy.NAME, "Available Plugins:"))) + # Enable the plugin + plugin_checkbox: WebElement = wait.until(EC.presence_of_element_located((AppiumBy.NAME, "Alternate Calendar"))) + plugin_checkbox.click() + self.driver.find_element(AppiumBy.NAME, "Apply").click() + # Switch back to Appearance so there will only be one "Alternate Calendar" match + self.driver.find_element(AppiumBy.NAME, "Appearance").click() + wait.until_not(lambda _: plugin_checkbox.is_displayed()) + # Switch to the calendar plugin + wait.until(EC.presence_of_element_located((AppiumBy.NAME, "Alternate Calendar"))).click() + wait.until(EC.presence_of_element_located((AppiumBy.NAME, "Calendar system:"))) + combobox_element = self.driver.find_element(AppiumBy.NAME, "Julian") + combobox_element.click() # index + 1 + combobox_element.click() # index + 1 + self.driver.find_element(AppiumBy.NAME, "Chinese Lunar Calendar") + + # Close the config dialog + self.driver.find_element(AppiumBy.NAME, "OK").click() + wait.until_not(lambda _: combobox_element.is_displayed()) + + # Open the widget and check if the sublabel is sucessfully loaded + self.driver.find_element(AppiumBy.ACCESSIBILITY_ID, "digital-clock-compactrepresentation").click() + try: + self.driver.find_element(AppiumBy.NAME, "廿一") + except NoSuchElementException: + self.driver.find_element(AppiumBy.NAME, "廿二") + + +if __name__ == '__main__': + unittest.main() diff --git a/appiumtests/keyboardindicatortest.py b/appiumtests/keyboardindicatortest.py new file mode 100755 index 00000000..9450a18d --- /dev/null +++ b/appiumtests/keyboardindicatortest.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 + +# SPDX-FileCopyrightText: 2024 Fushan Wen +# SPDX-License-Identifier: MIT + +import subprocess +import unittest +from typing import Final + +from appium import webdriver +from appium.options.common.base import AppiumOptions +from appium.webdriver.common.appiumby import AppiumBy + +WIDGET_ID: Final = "org.kde.plasma.keyboardindicator" + + +class KeyboardIndicatorTest(unittest.TestCase): + + driver: webdriver.Remote + + @classmethod + def setUpClass(cls) -> None: + options = AppiumOptions() + options.set_capability("app", f"plasmawindowed -p org.kde.plasma.nano {WIDGET_ID}") + options.set_capability("environ", { + "QT_FATAL_WARNINGS": "1", + "QT_LOGGING_RULES": "qt.accessibility.atspi.warning=false;qt.dbus.integration.warning=false;kf.plasma.core.warning=false;kf.windowsystem.warning=false;kf.kirigami.platform.warning=false", + }) + options.set_capability("timeouts", {'implicit': 10000}) + cls.driver = webdriver.Remote(command_executor='http://127.0.0.1:4723', options=options) + + @classmethod + def tearDownClass(cls) -> None: + # Make sure to terminate the driver again, lest it dangles. + cls.driver.quit() + + def tearDown(self) -> None: + pass + + def test_1_caps_lock(self) -> None: + self.driver.find_element(AppiumBy.NAME, "No lock keys activated") + subprocess.check_call(["xdotool", "key", "Caps_Lock"]) + self.driver.find_element(AppiumBy.NAME, "Caps Lock activated") + subprocess.check_call(["xdotool", "key", "Caps_Lock"]) + self.driver.find_element(AppiumBy.NAME, "No lock keys activated") + + def test_2_num_lock(self) -> None: + """ + Num lock indicator is disabled by default + """ + self.driver.find_element(AppiumBy.NAME, "No lock keys activated") + subprocess.check_call(["xdotool", "key", "Num_Lock"]) + self.driver.find_element(AppiumBy.NAME, "No lock keys activated") + subprocess.check_call(["xdotool", "key", "Num_Lock"]) + self.driver.find_element(AppiumBy.NAME, "No lock keys activated") + + +if __name__ == '__main__': + unittest.main() diff --git a/applets/CMakeLists.txt b/applets/CMakeLists.txt new file mode 100644 index 00000000..88f9d805 --- /dev/null +++ b/applets/CMakeLists.txt @@ -0,0 +1,21 @@ +plasma_install_package(binary-clock/package org.kde.plasma.binaryclock) +plasma_install_package(calculator/package org.kde.plasma.calculator) +plasma_install_package(fuzzy-clock/package org.kde.plasma.fuzzyclock) +plasma_install_package(katesessions org.kde.plasma.addons.katesessions) +plasma_install_package(keyboardindicator org.kde.plasma.keyboardindicator) +plasma_install_package(kickerdash org.kde.plasma.kickerdash) +plasma_install_package(konsoleprofiles/package org.kde.plasma.konsoleprofiles) +plasma_install_package(userswitcher/package org.kde.plasma.userswitcher) + +add_subdirectory(colorpicker) +add_subdirectory(comic) +add_subdirectory(dict) +add_subdirectory(diskquota) +add_subdirectory(fifteenPuzzle) +add_subdirectory(grouping) +add_subdirectory(mediaframe) +add_subdirectory(notes) +add_subdirectory(quicklaunch) +add_subdirectory(timer) +add_subdirectory(weather) +add_subdirectory(webbrowser) diff --git a/applets/binary-clock/Messages.sh b/applets/binary-clock/Messages.sh new file mode 100755 index 00000000..b723516f --- /dev/null +++ b/applets/binary-clock/Messages.sh @@ -0,0 +1,2 @@ +#! /usr/bin/env bash +$XGETTEXT `find . -name \*.qml` -o $podir/plasma_applet_org.kde.plasma.binaryclock.pot diff --git a/applets/binary-clock/package/contents/config/config.qml b/applets/binary-clock/package/contents/config/config.qml new file mode 100644 index 00000000..fd48932a --- /dev/null +++ b/applets/binary-clock/package/contents/config/config.qml @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2014 Joseph Wenninger + * SPDX-FileCopyrightText: 2018 Piotr Kąkol + * + * Based on analog-clock config.qml: + * SPDX-FileCopyrightText: 2013 Marco Martin + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.0 + +import org.kde.plasma.configuration 2.0 + +ConfigModel { + ConfigCategory { + name: i18nc("@title", "Appearance") + icon: "preferences-desktop-color" + source: "configGeneral.qml" + } +} diff --git a/applets/binary-clock/package/contents/config/main.xml b/applets/binary-clock/package/contents/config/main.xml new file mode 100644 index 00000000..37d65e05 --- /dev/null +++ b/applets/binary-clock/package/contents/config/main.xml @@ -0,0 +1,34 @@ + + + + + + + true + + + true + + + false + + + green + + + false + + + red + + + false + + + blue + + + \ No newline at end of file diff --git a/applets/binary-clock/package/contents/ui/BinaryClock.qml b/applets/binary-clock/package/contents/ui/BinaryClock.qml new file mode 100644 index 00000000..7fc0b4f0 --- /dev/null +++ b/applets/binary-clock/package/contents/ui/BinaryClock.qml @@ -0,0 +1,93 @@ +/* + * Rewrite of the KDE4-Plasma Binary Clock for KF5/Plasma/QML + * + * SPDX-FileCopyrightText: 2014 Joseph Wenninger + * SPDX-FileCopyrightText: 2018 Piotr Kąkol + * SPDX-FileCopyrightText: 2023 Bharadwaj Raju + * + * Original code (KDE4): + * SPDX-FileCopyrightText: 2007 Riccardo Iaconelli + * SPDX-FileCopyrightText: 2007 Davide Bettio + * + * Based on FuzzyClock.qml: + * SPDX-FileCopyrightText: 2013 Heena Mahour + * SPDX-FileCopyrightText: 2013 Sebastian Kügler + * SPDX-FileCopyrightText: 2013 Martin Klapetek + * SPDX-FileCopyrightText: 2014 David Edmundson + * SPDX-FileCopyrightText: 2014 Kai Uwe Broulik + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick +import QtQuick.Layouts +import org.kde.plasma.core as PlasmaCore +import org.kde.kirigami as Kirigami + +Item { + id: main + + readonly property real w1: (main.height-5*Kirigami.Units.smallSpacing)*dots/4 + + Layout.minimumWidth: w1 < 20 ? 20 : w1 + Layout.maximumWidth: Infinity + Layout.preferredWidth: Layout.minimumWidth + + Layout.minimumHeight: 16+(Kirigami.Units.smallSpacing*5) + //Layout.maximumHeight: vertical ? Layout.minimumHeight : Infinity + //Layout.preferredHeight: Layout.minimumHeight + + readonly property int formFactor: plasmoid.formFactor + + readonly property bool constrained: formFactor == PlasmaCore.Types.Vertical || formFactor == PlasmaCore.Types.Horizontal + + readonly property bool showSeconds: root.showSeconds + + readonly property int hours: root.hours + readonly property int minutes: root.minutes + readonly property int seconds: root.seconds + + readonly property int base: 10 + + readonly property bool showOffLeds: plasmoid.configuration.showOffLeds + + readonly property int dots: showSeconds ? 6 : 4 + + readonly property color onColor: plasmoid.configuration.useCustomColorForActive ? plasmoid.configuration.customColorForActive : Kirigami.Theme.textColor + readonly property color offColor: plasmoid.configuration.useCustomColorForInactive ? plasmoid.configuration.customColorForInactive : Qt.rgba(onColor.r, onColor.g, onColor.b, 0.2) + + readonly property int dotSize: Math.min((height-5*Kirigami.Units.smallSpacing)/4, (width-(dots+1)*Kirigami.Units.smallSpacing)/dots) + + property bool wasExpanded: false + + readonly property alias mouseArea: mouseArea + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + onPressed: wasExpanded = root.expanded + onClicked: root.expanded = !wasExpanded + } + + GridLayout { + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + columns: main.showSeconds ? 6 : 4 + Repeater { + model: [8, 4, 2, 1] + Repeater { + model: [hours/base, hours%base, minutes/base, minutes%base, seconds/base, seconds%base] + property var bit: modelData + Rectangle { + property var timeVal: modelData + visible: main.dotSize >= 0 && (main.showSeconds || index < 4) + width: main.dotSize + height: width + radius: width/2 + color: (timeVal & bit) ? main.onColor : (main.showOffLeds ? main.offColor : "transparent") + } + } + } + } +} diff --git a/applets/binary-clock/package/contents/ui/configGeneral.qml b/applets/binary-clock/package/contents/ui/configGeneral.qml new file mode 100644 index 00000000..0ca0b454 --- /dev/null +++ b/applets/binary-clock/package/contents/ui/configGeneral.qml @@ -0,0 +1,68 @@ +/* + * SP9X-FileCopyrightText: 2014 Joseph Wenninger + * SPDX-FileCopyrightText: 2018 Piotr Kąkol + * + * Based on analog-clock configGeneral.qml: + * SPDX-FileCopyrightText: 2013 David Edmundson + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.0 +import QtQuick.Controls 2.5 as QtControls +import QtQuick.Layouts 1.1 +import org.kde.kquickcontrols 2.0 as KQuickControls +import org.kde.kirigami 2.5 as Kirigami +import org.kde.kcmutils as KCM + +KCM.SimpleKCM { + property alias cfg_showOffLeds: showOffLedsCheckBox.checked + property alias cfg_showSeconds: showSecondsCheckBox.checked + property alias cfg_useCustomColorForActive: useCustomColorForActiveCheckBox.checked + property alias cfg_customColorForActive: customColorForActive.color + property alias cfg_useCustomColorForInactive: useCustomColorForInactiveCheckBox.checked + property alias cfg_customColorForInactive: customColorForInactive.color + + Kirigami.FormLayout { + QtControls.CheckBox { + id: showOffLedsCheckBox + text: i18nc("@option:check", "Inactive lights") + } + + QtControls.CheckBox { + id: showSecondsCheckBox + text: i18nc("@option:check", "Seconds") + } + + Item { + Kirigami.FormData.isSection:true + } + + RowLayout { + Kirigami.FormData.label:i18n("Use custom color for:") + + QtControls.CheckBox { + id: useCustomColorForActiveCheckBox + text: i18nc("@option:check", "Active lights") + } + + KQuickControls.ColorButton { + id: customColorForActive + enabled: useCustomColorForActiveCheckBox.checked + } + } + + RowLayout { + + QtControls.CheckBox { + id: useCustomColorForInactiveCheckBox + text: i18nc("@option:check", "Inactive lights") + } + + KQuickControls.ColorButton { + id:customColorForInactive + enabled: useCustomColorForInactiveCheckBox.checked + } + } + } +} diff --git a/applets/binary-clock/package/contents/ui/main.qml b/applets/binary-clock/package/contents/ui/main.qml new file mode 100644 index 00000000..61280465 --- /dev/null +++ b/applets/binary-clock/package/contents/ui/main.qml @@ -0,0 +1,67 @@ +/* + * SPDX-FileCopyrightText: 2014 Joseph Wenninger + * SPDX-FileCopyrightText: 2018 Piotr Kąkol + * + * Based on fuzzy-clock main.qml: + * SPDX-FileCopyrightText: 2013 Heena Mahour + * SPDX-FileCopyrightText: 2013 Sebastian Kügler + * SPDX-FileCopyrightText: 2014 Kai Uwe Broulik + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQml +import QtQuick 2.0 +import QtQuick.Layouts 1.1 + +import org.kde.plasma.plasmoid 2.0 +import org.kde.kirigami 2.20 as Kirigami +import org.kde.plasma.plasma5support 2.0 as P5Support +import org.kde.plasma.core as PlasmaCore +import org.kde.plasma.extras 2.0 as PlasmaExtras +import org.kde.plasma.workspace.calendar 2.0 as PlasmaCalendar + +PlasmoidItem { + id: root + + property bool showSeconds: plasmoid.configuration.showSeconds + property int hours + property int minutes + property int seconds + width: Kirigami.Units.gridUnit * 10 + height: Kirigami.Units.gridUnit * 4 + + Plasmoid.backgroundHints: PlasmaCore.Types.DefaultBackground | PlasmaCore.Types.ConfigurableBackground + preferredRepresentation: compactRepresentation + + // keep this consistent with toolTipMainText and toolTipSubText in analog-clock + toolTipMainText: Qt.locale().toString(dataSource.data["Local"]["DateTime"],"dddd") + toolTipSubText: Qt.locale().toString(dataSource.data["Local"]["DateTime"], Qt.locale().timeFormat(Locale.LongFormat)) + "\n" + Qt.locale().toString(dataSource.data["Local"]["DateTime"], Qt.locale().dateFormat(Locale.LongFormat).replace(/(^dddd.?\s)|(,?\sdddd$)/, "")) + + P5Support.DataSource { + id: dataSource + engine: "time" + connectedSources: ["Local"] + intervalAlignment: plasmoid.configuration.showSeconds || compactRepresentationItem.mouseArea.containsMouse ? P5Support.Types.NoAlignment : P5Support.Types.AlignToMinute + interval: showSeconds || compactRepresentationItem.mouseArea.containsMouse ? 1000 : 60000 + + onDataChanged: { + var date = new Date(data["Local"]["DateTime"]); + hours = date.getHours(); + minutes = date.getMinutes(); + seconds = date.getSeconds(); + } + Component.onCompleted: { + dataChanged(); + } + } + + compactRepresentation: BinaryClock { } + + fullRepresentation: PlasmaCalendar.MonthView { + Layout.minimumWidth: Kirigami.Units.gridUnit * 20 + Layout.minimumHeight: Kirigami.Units.gridUnit * 20 + + today: dataSource.data["Local"]["DateTime"] + } +} diff --git a/applets/binary-clock/package/metadata.json b/applets/binary-clock/package/metadata.json new file mode 100644 index 00000000..18d551dc --- /dev/null +++ b/applets/binary-clock/package/metadata.json @@ -0,0 +1,243 @@ +{ + "KPackageStructure": "Plasma/Applet", + "KPlugin": { + "Authors": [ + { + "Email": "jowenn@kde.org", + "Name": "Joseph Wenninger", + "Name[ar]": "جوزيف وينينجر", + "Name[az]": "Joseph Wenninger", + "Name[bg]": "Joseph Wenninger", + "Name[ca@valencia]": "Joseph Wenninger", + "Name[ca]": "Joseph Wenninger", + "Name[cs]": "Joseph Wenninger", + "Name[da]": "Joseph Wenninger", + "Name[de]": "Joseph Wenninger", + "Name[en_GB]": "Joseph Wenninger", + "Name[eo]": "Joseph Wenninger", + "Name[es]": "Joseph Wenninger", + "Name[eu]": "Joseph Wenninger", + "Name[fi]": "Joseph Wenninger", + "Name[fr]": "Joseph Wenninger", + "Name[gl]": "Joseph Wenninger", + "Name[he]": "ג׳וזף ונינגר", + "Name[hu]": "Joseph Wenninger", + "Name[ia]": "Joseph Wenninger", + "Name[id]": "Joseph Wenninger", + "Name[is]": "Joseph Wenninger", + "Name[it]": "Joseph Wenninger", + "Name[ja]": "Joseph Wenninger", + "Name[ka]": "Joseph Wenninger", + "Name[ko]": "Joseph Wenninger", + "Name[lt]": "Joseph Wenninger", + "Name[lv]": "Joseph Wenninger", + "Name[nl]": "Joseph Wenninger", + "Name[nn]": "Joseph Wenninger", + "Name[pl]": "Joseph Wenninger", + "Name[pt]": "Joseph Wenninger", + "Name[pt_BR]": "Joseph Wenninger", + "Name[ro]": "Joseph Wenninger", + "Name[ru]": "Joseph Wenninger", + "Name[sk]": "Joseph Wenninger", + "Name[sl]": "Joseph Wenninger", + "Name[sv]": "Joseph Wenninger", + "Name[tr]": "Joseph Wenninger", + "Name[uk]": "Joseph Wenninger", + "Name[vi]": "Joseph Wenninger", + "Name[x-test]": "xxJoseph Wenningerxx", + "Name[zh_CN]": "Joseph Wenninger", + "Name[zh_TW]": "Joseph Wenninger" + }, + { + "Email": "davide.bettio@kdemail.net", + "Name": "Davide Bettio", + "Name[ar]": "ديفيد بيتيو", + "Name[az]": "Davide Bettio", + "Name[bg]": "Davide Bettio", + "Name[ca@valencia]": "Davide Bettio", + "Name[ca]": "Davide Bettio", + "Name[cs]": "Davide Bettio", + "Name[da]": "Davide Bettio", + "Name[de]": "Davide Bettio", + "Name[en_GB]": "Davide Bettio", + "Name[eo]": "Davide Bettio", + "Name[es]": "Davide Bettio", + "Name[eu]": "Davide Bettio", + "Name[fi]": "Davide Bettio", + "Name[fr]": "Davide Bettio", + "Name[gl]": "Davide Bettio", + "Name[he]": "דוד בטיו", + "Name[hu]": "Davide Bettio", + "Name[ia]": "Davide Bettio", + "Name[id]": "Davide Bettio", + "Name[is]": "Davide Bettio", + "Name[it]": "Davide Bettio", + "Name[ja]": "Davide Bettio", + "Name[ka]": "Davide Bettio", + "Name[ko]": "Davide Bettio", + "Name[lt]": "Davide Bettio", + "Name[lv]": "Davide Bettio", + "Name[nl]": "Davide Bettio", + "Name[nn]": "Davide Bettio", + "Name[pl]": "Davide Bettio", + "Name[pt]": "Davide Bettio", + "Name[pt_BR]": "Davide Bettio", + "Name[ro]": "Davide Bettio", + "Name[ru]": "Davide Bettio", + "Name[sk]": "Davide Bettio", + "Name[sl]": "Davide Bettio", + "Name[sv]": "Davide Bettio", + "Name[tr]": "Davide Bettio", + "Name[uk]": "Davide Bettio", + "Name[vi]": "Davide Bettio", + "Name[x-test]": "xxDavide Bettioxx", + "Name[zh_CN]": "Davide Bettio", + "Name[zh_TW]": "Davide Bettio" + }, + { + "Email": "piotrkakol@protonmail.com", + "Name": "Piotr Kąkol", + "Name[ar]": "بيوتر كانكول", + "Name[az]": "Piotr Kąkol", + "Name[bg]": "Piotr Kąkol", + "Name[ca@valencia]": "Piotr Kąkol", + "Name[ca]": "Piotr Kąkol", + "Name[cs]": "Piotr Kąkol", + "Name[da]": "Piotr Kąkol", + "Name[de]": "Piotr Kąkol", + "Name[en_GB]": "Piotr Kąkol", + "Name[eo]": "Piotr Kąkol", + "Name[es]": "Piotr Kąkol", + "Name[eu]": "Piotr Kąkol", + "Name[fi]": "Piotr Kąkol", + "Name[fr]": "Piotr Kąkol", + "Name[gl]": "Piotr Kąkol", + "Name[he]": "פיוטר קוקול", + "Name[hu]": "Piotr Kąkol", + "Name[ia]": "Piotr Kąkol", + "Name[id]": "Piotr Kąkol", + "Name[is]": "Piotr Kąkol", + "Name[it]": "Piotr Kąkol", + "Name[ja]": "Piotr Kąkol", + "Name[ka]": "Piotr Kąkol", + "Name[ko]": "Piotr Kąkol", + "Name[lt]": "Piotr Kąkol", + "Name[lv]": "Piotr Kąkol", + "Name[nl]": "Piotr Kąkol", + "Name[nn]": "Piotr Kąkol", + "Name[pl]": "Piotr Kąkol", + "Name[pt]": "Piotr Kąkol", + "Name[pt_BR]": "Piotr Kąkol", + "Name[ro]": "Piotr Kąkol", + "Name[ru]": "Piotr Kąkol", + "Name[sk]": "Piotr Kąkol", + "Name[sl]": "Piotr Kąkol", + "Name[sv]": "Piotr Kąkol", + "Name[tr]": "Piotr Kąkol", + "Name[uk]": "Piotr Kąkol", + "Name[vi]": "Piotr Kąkol", + "Name[x-test]": "xxPiotr Kąkolxx", + "Name[zh_CN]": "Piotr Kąkol", + "Name[zh_TW]": "Piotr Kąkol" + } + ], + "BugReportUrl": "https://bugs.kde.org/enter_bug.cgi?product=kdeplasma-addons&component=Binary Clock", + "Category": "Date and Time", + "Description": "Time displayed in binary format", + "Description[ar]": "الوقت يظهر بالتنسيق الثنائي", + "Description[az]": "Vaxt, binar formatında göstərilir", + "Description[bg]": "Представяне на часа в бинарен формат", + "Description[ca@valencia]": "Mostra l'hora en format binari", + "Description[ca]": "Mostra l'hora en format binari", + "Description[cs]": "Zobrazení času v binárním formátu", + "Description[da]": "Tid vist i binært format", + "Description[de]": "Zeitanzeige in Binärformat", + "Description[en_GB]": "Time displayed in binary format", + "Description[eo]": "Tempo montrita en binara formato", + "Description[es]": "Hora mostrada en formato binario", + "Description[eu]": "Ordua formatu bitarrean bistaratua", + "Description[fi]": "Binaarimuodossa esitetty kellonaika", + "Description[fr]": "Heure affichée dans un format numérique", + "Description[gl]": "Mostra a hora no formato binario", + "Description[he]": "הצגת הזמן בצורה בינרית", + "Description[hu]": "Idő bináris formátumban megjelenítve", + "Description[ia]": "Tempore monstrate in formato binari", + "Description[id]": "Waktu ditampilkan dalam format binari", + "Description[is]": "Sýnir tímann á tvíundarformi", + "Description[it]": "Ora mostrata in formato binario", + "Description[ja]": "時間をバイナリ形式で表示します", + "Description[ka]": "ორობითში ნაჩვენები დრო", + "Description[ko]": "이진수로 시각 표시", + "Description[lt]": "Dvejetainiu formatu rodomas laikas", + "Description[lv]": "Binārā formātā parādÄ«ts laiks", + "Description[nl]": "De tijd in binair formaat", + "Description[nn]": "Klokka vist i binærformat", + "Description[pl]": "Wyświetla czas w formacie dwójkowym", + "Description[pt]": "A hora apresentada no formato binário", + "Description[pt_BR]": "Hora exibida em formato binário", + "Description[ro]": "Ora afișată în format binar", + "Description[ru]": "Время в двоичной записи", + "Description[sk]": "Čas zobrazený v binárnom tvare", + "Description[sl]": "Čas prikazan v binarnem formatu", + "Description[sv]": "Tid visad med binärformat", + "Description[tr]": "İkili biçimde gösterilen zaman", + "Description[uk]": "Час, показаний у двійковому форматі", + "Description[vi]": "Thời gian hiển thị ở dạng nhị phân", + "Description[x-test]": "xxTime displayed in binary formatxx", + "Description[zh_CN]": "以二进制格式显示时间", + "Description[zh_TW]": "以二進位制顯示時間", + "Icon": "clock", + "Id": "org.kde.plasma.binaryclock", + "License": "GPL-2.0+", + "Name": "Binary Clock", + "Name[ar]": "ساعة ثنائية", + "Name[ast]": "Reló binariu", + "Name[az]": "Binar saat", + "Name[bg]": "Двоичен часовник", + "Name[ca@valencia]": "Rellotge binari", + "Name[ca]": "Rellotge binari", + "Name[cs]": "Binární hodiny", + "Name[da]": "Binært ur", + "Name[de]": "Binäre Uhr", + "Name[en_GB]": "Binary Clock", + "Name[eo]": "Binara Horloĝo", + "Name[es]": "Reloj binario", + "Name[eu]": "Ordulari bitarra", + "Name[fi]": "Binaarikello", + "Name[fr]": "Horloge numérique", + "Name[gl]": "Reloxo binario", + "Name[he]": "שעון בינרי", + "Name[hu]": "Bináris óra", + "Name[ia]": "Horologio binari", + "Name[id]": "Jam Binari", + "Name[is]": "Tvíundarklukka", + "Name[it]": "Orologio binario", + "Name[ja]": "バイナリ時計", + "Name[ka]": "ბინარული საათი", + "Name[ko]": "바이너리 시계", + "Name[lt]": "Dvejetainis laikrodis", + "Name[lv]": "Binārais pulkstenis", + "Name[nl]": "Binaire klok", + "Name[nn]": "Binær­klokke", + "Name[pl]": "Zegar dwójkowy", + "Name[pt]": "Relógio Binário", + "Name[pt_BR]": "Relógio binário", + "Name[ro]": "Ceas binar", + "Name[ru]": "Двоичные часы", + "Name[sk]": "Binárne hodiny", + "Name[sl]": "Binarna ura", + "Name[sv]": "Binärklocka", + "Name[tr]": "İkili Saat", + "Name[uk]": "Двійковий годинник", + "Name[vi]": "Đồng hồ nhị phân", + "Name[x-test]": "xxBinary Clockxx", + "Name[zh_CN]": "二进制时钟", + "Name[zh_TW]": "二進位時鐘", + "Website": "https://kde.org/plasma-desktop" + }, + "X-Plasma-API-Minimum-Version": "6.0", + "X-Plasma-Provides": [ + "org.kde.plasma.time", + "org.kde.plasma.date" + ] +} diff --git a/applets/calculator/Messages.sh b/applets/calculator/Messages.sh new file mode 100755 index 00000000..8c16149c --- /dev/null +++ b/applets/calculator/Messages.sh @@ -0,0 +1,2 @@ +#! /usr/bin/env bash +$XGETTEXT `find . -name \*.qml` -o $podir/plasma_applet_org.kde.plasma.calculator.pot diff --git a/applets/calculator/package/contents/ui/main.qml b/applets/calculator/package/contents/ui/main.qml new file mode 100644 index 00000000..1342c1fd --- /dev/null +++ b/applets/calculator/package/contents/ui/main.qml @@ -0,0 +1,586 @@ +/* + * SPDX-FileCopyrightText: 2015 Bernhard Friedreich + * SPDX-FileCopyrightText: 2014 Martin Yrjölä + * SPDX-FileCopyrightText: 2012, 2014 Davide Bettio + * SPDX-FileCopyrightText: 2012, 2014 David Edmundson + * SPDX-FileCopyrightText: 2012 Luiz Romário Santana Rios + * SPDX-FileCopyrightText: 2007 Henry Stanaland + * SPDX-FileCopyrightText: 2008 Laurent Montel + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.5 +import QtQuick.Layouts 1.3 +import QtQuick.Controls 2.5 as QQC2 +import org.kde.kirigami 2.20 as Kirigami +import org.kde.ksvg 1.0 as KSvg +import org.kde.plasma.plasma5support 2.0 as P5Support +import org.kde.plasma.components 3.0 as PlasmaComponents +import org.kde.plasma.plasmoid + +PlasmoidItem { + id: main; + + switchWidth: Kirigami.Units.gridUnit * 7 + switchHeight: Math.round(Kirigami.Units.gridUnit * 10.5) + + // Make the buttons' text labels scale with the widget's size + // This is propagated down to all child controls with text + + property real result: 0; + property bool hasResult: false; + property bool showingInput: true; + property bool showingResult: false; + property string operator + property real operand: 0; + property bool commaPressed: false; + property int decimals: 0; + property int inputSize: 0; + property TextEdit display + + readonly property int maxInputLength: 18; // More than that and the number notation + // turns scientific (i.e.: 1.32324e+12). + // When calculating 1/3 the answer is + // 18 characters long. + + function digitClicked(digit) { + if (showingResult) { + allClearClicked(); + } + + if (commaPressed) { + ++decimals; + var tenToTheDecimals = Math.pow(10, decimals); + operand = (operand * tenToTheDecimals + digit) / tenToTheDecimals; + } else { + operand = operand * 10 + digit; + } + showingInput = true; + displayNumber(operand); + ++inputSize; + } + + function deleteDigit() { + if (showingResult) { + allClearClicked(); + } + + if (showingInput) { + if (commaPressed) { + if (decimals === 0) { + commaPressed = false; + } else if (decimals > 0) { + operand -= operand % Math.pow(10, 1 - decimals); + --decimals; + --inputSize; + } + } else if (inputSize > 0) { + operand = (operand - (operand % 10)) / 10; + --inputSize; + } + } + displayNumber(operand); + } + + function decimalClicked() { + if (showingResult) { + allClearClicked(); + } + + commaPressed = true; + showingInput = true; + displayNumber(operand); + } + + function doOperation() { + switch (operator) { + case "+": + result += operand; + break; + case "-": + result -= operand; + break; + case "*": + result *= operand; + break; + case "/": + if (operand === 0) { + divisionByZero(); + return; + } + result /= operand; + break; + default: + return; + } + + showingInput = false; + displayNumber(result); + } + + function clearOperand() { + operand = 0; + commaPressed = false; + decimals = 0; + inputSize = 0; + } + + function setOperator(op) { + if (!hasResult) { + result = operand; + hasResult = true; + } else if (showingInput) { + doOperation(); + } + + clearOperand(); + operator = op; + showingResult = false; + } + + function equalsClicked() { + showingResult = true; + doOperation(); + } + + function clearClicked() { + clearOperand(); + operator = ""; + displayNumber(operand); + showingInput = true; + showingResult = false; + } + + function allClearClicked() { + clearClicked(); + result = 0; + hasResult = false; + } + + function algarismCount(number) { + return number == 0? 1 : + Math.floor(Math.log(Math.abs(number))/Math.log(10)) + 1; + } + + // use the clipboard datasource for being able to inspect the clipboard content before pasting it + P5Support.DataSource { + id: clipboardSource + property bool editing: false; + engine: "org.kde.plasma.clipboard" + connectedSources: "clipboard" + } + + function copyToClipboard() { + display.selectAll(); + display.copy(); + display.deselect(); + } + + function pasteFromClipboard() { + var content = clipboardSource.data["clipboard"]["current"]; + if (content != "") { + content = content.trim(); + } + + // check if the clipboard content as a whole is a valid number (without sign, no operators, ...) + if (isValidClipboardInput(content)) { + var digitRegex = new RegExp('^[0-9]$'); + var decimalRegex = new RegExp('^[\.,]$'); + + for (var i = 0; i < content.length; i++) { + if (digitRegex.test(content[i])) { + digitClicked(parseInt(content[i])); + } else if (decimalRegex.test(content[i])) { + decimalClicked(); + } + } + } + } + + function isValidClipboardInput(input) { + return new RegExp('^[0-9]*[\.,]?[0-9]+$').test(input); + } + + function displayNumber(number) { + var text = number.toLocaleString(Qt.locale(), "g", 14); + + // Show all decimals including zeroes and show decimalPoint + if (showingInput && commaPressed) { + text = number.toLocaleString(Qt.locale(), "f", decimals); + if (decimals === 0) { + text += Qt.locale().decimalPoint; + } + } + display.text = text; + + var decimalsToShow = 9; + // Decrease precision until the text fits to the display. + while (display.contentWidth > display.width && decimalsToShow > 0) { + display.text = number.toLocaleString(Qt.locale(), "g", decimalsToShow--); + } + } + + function divisionByZero() { + showingInput = false; + showingResult = true; + display.text = i18nc("Abbreviation for result (undefined) of division by zero, max. six to nine characters.", "undef"); + } + + fullRepresentation: QQC2.Control { + // Make the buttons' text labels scale with the widget's size + // This is propagated down to all child controls with text + font.pixelSize: Math.round(width/12) + padding: 0 + Layout.minimumWidth: main.switchWidth + Layout.minimumHeight: main.switchHeight + Layout.preferredWidth: main.switchWidth * 2 + Layout.preferredHeight: main.switchHeight * 2 + + contentItem: ColumnLayout { + id: mainLayout + anchors.fill: parent + anchors.margins: 4 + + focus: true; + spacing: 4; + + Keys.onDigit0Pressed: { digitClicked(0); zeroButton.forceActiveFocus(Qt.TabFocusReason); } + Keys.onDigit1Pressed: { digitClicked(1); oneButton.forceActiveFocus(Qt.TabFocusReason); } + Keys.onDigit2Pressed: { digitClicked(2); twoButton.forceActiveFocus(Qt.TabFocusReason); } + Keys.onDigit3Pressed: { digitClicked(3); threeButton.forceActiveFocus(Qt.TabFocusReason); } + Keys.onDigit4Pressed: { digitClicked(4); fourButton.forceActiveFocus(Qt.TabFocusReason); } + Keys.onDigit5Pressed: { digitClicked(5); fiveButton.forceActiveFocus(Qt.TabFocusReason); } + Keys.onDigit6Pressed: { digitClicked(6); sixButton.forceActiveFocus(Qt.TabFocusReason); } + Keys.onDigit7Pressed: { digitClicked(7); sevenButton.forceActiveFocus(Qt.TabFocusReason); } + Keys.onDigit8Pressed: { digitClicked(8); eightButton.forceActiveFocus(Qt.TabFocusReason); } + Keys.onDigit9Pressed: { digitClicked(9); nineButton.forceActiveFocus(Qt.TabFocusReason); } + Keys.onEscapePressed: { allClearClicked(); allClearButton.forceActiveFocus(Qt.TabFocusReason); } + Keys.onDeletePressed: { clearClicked(); clearButton.forceActiveFocus(Qt.TabFocusReason); } + Keys.onPressed: { + switch (event.key) { + case Qt.Key_Plus: + setOperator("+"); + plusButton.forceActiveFocus(Qt.TabFocusReason); + break; + case Qt.Key_Minus: + setOperator("-"); + minusButton.forceActiveFocus(Qt.TabFocusReason); + break; + case Qt.Key_Asterisk: + setOperator("*"); + multiplyButton.forceActiveFocus(Qt.TabFocusReason); + break; + case Qt.Key_Slash: + setOperator("/"); + divideButton.forceActiveFocus(Qt.TabFocusReason); + break; + case Qt.Key_Comma: + case Qt.Key_Period: + decimalClicked(); + decimalButton.forceActiveFocus(Qt.TabFocusReason); + break; + case Qt.Key_Equal: + case Qt.Key_Return: + case Qt.Key_Enter: + equalsClicked(); + break; + case Qt.Key_Backspace: + deleteDigit(); + display.forceActiveFocus(Qt.TabFocusReason); + break; + default: + if (event.matches(StandardKey.Copy)) { + copyToClipboard(); + } else if (event.matches(StandardKey.Paste)) { + pasteFromClipboard(); + } + break; + } + } + + KeyNavigation.up: zeroButton + KeyNavigation.down: clearButton + KeyNavigation.left: allClearButton + KeyNavigation.right: clearButton + + KSvg.FrameSvgItem { + id: displayFrame; + Layout.fillWidth: true + Layout.minimumHeight: 2 * display.font.pixelSize; + imagePath: "widgets/frame"; + prefix: "plain"; + + TextEdit { + id: display; + anchors { + fill: parent; + margins: parent.margins.right; + } + text: "0"; + font.pointSize: Kirigami.Theme.defaultFont.pointSize * 2; + font.weight: Font.Bold; + Kirigami.Theme.colorSet: Kirigami.Theme.View + color: Kirigami.Theme.textColor + horizontalAlignment: TextEdit.AlignRight; + verticalAlignment: TextEdit.AlignVCenter; + readOnly: true; + + focus: main.expanded + + Accessible.name: text + Accessible.description: i18nc("@label calculation result", "Result") + + Binding { + target: main + property: "display" + value: display + } + } + } + + GridLayout { + id: buttonsGrid; + columns: 4; + rows: 5; + columnSpacing: 4 + rowSpacing: 4 + + Layout.fillWidth: true + Layout.fillHeight: true + + PlasmaComponents.Button { + id: clearButton + + Layout.fillWidth: true + Layout.fillHeight: true + + KeyNavigation.down: sevenButton + KeyNavigation.right: divideButton + + text: i18nc("Text of the clear button", "C"); + onClicked: clearClicked(); + } + + PlasmaComponents.Button { + id: divideButton + + Layout.fillWidth: true + Layout.fillHeight: true + + KeyNavigation.down: eightButton + KeyNavigation.right: multiplyButton + + text: i18nc("Text of the division button", "÷"); + onClicked: setOperator("/"); + } + + PlasmaComponents.Button { + id: multiplyButton + + Layout.fillWidth: true + Layout.fillHeight: true + + KeyNavigation.down: nineButton + KeyNavigation.right: allClearButton + + text: i18nc("Text of the multiplication button", "×"); + onClicked: setOperator("*"); + } + + PlasmaComponents.Button { + id: allClearButton + + Layout.fillWidth: true + Layout.fillHeight: true + + KeyNavigation.down: minusButton + + text: i18nc("Text of the all clear button", "AC"); + onClicked: allClearClicked(); + } + + + PlasmaComponents.Button { + id: sevenButton + + Layout.fillWidth: true + Layout.fillHeight: true + + KeyNavigation.down: fourButton + KeyNavigation.right: eightButton + + text: "7"; + onClicked: digitClicked(7); + } + + PlasmaComponents.Button { + id: eightButton + + Layout.fillWidth: true + Layout.fillHeight: true + + KeyNavigation.down: fiveButton + KeyNavigation.right: nineButton + + text: "8"; + onClicked: digitClicked(8); + } + + PlasmaComponents.Button { + id: nineButton + + Layout.fillWidth: true + Layout.fillHeight: true + + KeyNavigation.down: sixButton + KeyNavigation.right: minusButton + + text: "9"; + onClicked: digitClicked(9); + } + + PlasmaComponents.Button { + id: minusButton + + Layout.fillWidth: true + Layout.fillHeight: true + + KeyNavigation.down: plusButton + + text: i18nc("Text of the minus button", "-"); + onClicked: setOperator("-"); + } + + + PlasmaComponents.Button { + id: fourButton + + Layout.fillWidth: true + Layout.fillHeight: true + + KeyNavigation.down: oneButton + KeyNavigation.right: fiveButton + + text: "4"; + onClicked: digitClicked(4); + } + + PlasmaComponents.Button { + id: fiveButton + + Layout.fillWidth: true + Layout.fillHeight: true + + KeyNavigation.down: twoButton + KeyNavigation.right: sixButton + + text: "5"; + onClicked: digitClicked(5); + } + + PlasmaComponents.Button { + id: sixButton + + Layout.fillWidth: true + Layout.fillHeight: true + + KeyNavigation.down: threeButton + KeyNavigation.right: plusButton + + text: "6"; + onClicked: digitClicked(6); + } + + PlasmaComponents.Button { + id: plusButton + + Layout.fillWidth: true + Layout.fillHeight: true + + KeyNavigation.down: ansButton + + text: i18nc("Text of the plus button", "+"); + onClicked: setOperator("+"); + } + + + PlasmaComponents.Button { + id: oneButton + + Layout.fillWidth: true + Layout.fillHeight: true + + KeyNavigation.down: zeroButton + KeyNavigation.right: twoButton + + text: "1"; + onClicked: digitClicked(1); + } + + PlasmaComponents.Button { + id: twoButton + + Layout.fillWidth: true + Layout.fillHeight: true + + KeyNavigation.down: zeroButton + KeyNavigation.right: threeButton + + text: "2"; + onClicked: digitClicked(2); + } + + PlasmaComponents.Button { + id: threeButton + + Layout.fillWidth: true + Layout.fillHeight: true + + KeyNavigation.down: decimalButton + KeyNavigation.right: ansButton + + text: "3"; + onClicked: digitClicked(3); + } + + PlasmaComponents.Button { + id: ansButton + + Layout.fillWidth: true + Layout.fillHeight: true + + Layout.rowSpan: 2 + text: i18nc("Text of the equals button", "="); + onClicked: equalsClicked(); + } + + PlasmaComponents.Button { + id: zeroButton + + Layout.fillWidth: true + Layout.fillHeight: true + + KeyNavigation.right: decimalButton + + Layout.columnSpan: 2 + text: "0"; + onClicked: digitClicked(0); + } + + PlasmaComponents.Button { + id: decimalButton + + Layout.fillWidth: true + Layout.fillHeight: true + + KeyNavigation.right: ansButton + + text: Qt.locale().decimalPoint; + onClicked: decimalClicked(); + } + } + } + } +} + diff --git a/applets/calculator/package/metadata.json b/applets/calculator/package/metadata.json new file mode 100644 index 00000000..4e252c50 --- /dev/null +++ b/applets/calculator/package/metadata.json @@ -0,0 +1,146 @@ +{ + "KPackageStructure": "Plasma/Applet", + "KPlugin": { + "Authors": [ + { + "Email": "stanaland@gmail.com, bettio@kde.org", + "Name": "Henry Stanaland, Davide Bettio", + "Name[ar]": "هنري ستانالاند، ديفيد بيتيو", + "Name[az]": "Henry Stanaland, Davide Bettio", + "Name[bg]": "Henry Stanaland, Davide Bettio", + "Name[ca@valencia]": "Henry Stanaland, Davide Bettio", + "Name[ca]": "Henry Stanaland, Davide Bettio", + "Name[cs]": "Henry Stanaland, Davide Bettio", + "Name[da]": "Henry Stanaland, Davide Bettio", + "Name[de]": "Henry Stanaland, Davide Bettio", + "Name[en_GB]": "Henry Stanaland, Davide Bettio", + "Name[eo]": "Henry Stanaland, Davide Bettio", + "Name[es]": "Henry Stanaland, Davide Bettio", + "Name[eu]": "Henry Stanaland, Davide Bettio", + "Name[fi]": "Henry Stanaland, Davide Bettio", + "Name[fr]": "Henry Stanaland, Davide Bettio", + "Name[gl]": "Henry Stanaland, Davide Bettio", + "Name[he]": "הנרי סטנלנד, דוד בטיו", + "Name[hu]": "Henry Stanaland, Davide Bettio", + "Name[ia]": "Henry Stanaland, Davide Bettio", + "Name[id]": "Henry Stanaland, Davide Bettio", + "Name[is]": "Henry Stanaland, Davide Bettio", + "Name[it]": "Henry Stanaland, Davide Bettio", + "Name[ja]": "Henry Stanaland, Davide Bettio", + "Name[ka]": "Henry Stanaland, Davide Bettio", + "Name[ko]": "Henry Stanaland, Davide Bettio", + "Name[lt]": "Henry Stanaland, Davide Bettio", + "Name[lv]": "Henry Stanaland, Davide Bettio", + "Name[nl]": "Henry Stanaland, Davide Bettio", + "Name[nn]": "Henry Stanaland, Davide Bettio", + "Name[pl]": "Henry Stanaland, Davide Bettio", + "Name[pt]": "Henry Stanaland, Davide Bettio", + "Name[pt_BR]": "Henry Stanaland, Davide Bettio", + "Name[ro]": "Henry Stanaland, Davide Bettio", + "Name[ru]": "Henry Stanaland, Davide Bettio", + "Name[sk]": "Henry Stanaland, Davide Bettio", + "Name[sl]": "Henry Stanaland, Davide Bettio", + "Name[sv]": "Henry Stanaland, Davide Bettio", + "Name[tr]": "Henry Stanaland, Davide Bettio", + "Name[uk]": "Henry Stanaland, Davide Bettio", + "Name[vi]": "Henry Stanaland, Davide Bettio", + "Name[x-test]": "xxHenry Stanaland, Davide Bettioxx", + "Name[zh_CN]": "Henry Stanaland, Davide Bettio", + "Name[zh_TW]": "Henry Stanaland, Davide Bettio" + } + ], + "BugReportUrl": "https://bugs.kde.org/enter_bug.cgi?product=kdeplasma-addons&component=calculator", + "Category": "Utilities", + "Description": "Calculate simple sums", + "Description[ar]": "احسب عمليات جمع بسيطة", + "Description[az]": "Sadə hesablama", + "Description[bg]": "Изчисляване на прости суми", + "Description[ca@valencia]": "Calcula sumes senzilles", + "Description[ca]": "Calcula sumes senzilles", + "Description[cs]": "Jednoduché sčítání", + "Description[da]": "Udregn simple summer", + "Description[de]": "Für kleine Rechenaufgaben", + "Description[en_GB]": "Calculate simple sums", + "Description[eo]": "Kalkuli simplajn sumojn", + "Description[es]": "Calcular sumas sencillas", + "Description[eu]": "Gehiketa errazak kalkulatzea", + "Description[fi]": "Laske yksinkertaisia summia", + "Description[fr]": "Calculer de simples sommes", + "Description[gl]": "Calcula sumas simples", + "Description[he]": "חישוב סכומים פשוטים", + "Description[hu]": "Egyszerű összegek kiszámítása", + "Description[ia]": "Calcula simple summas", + "Description[id]": "Hitung penjumlahan sederhana", + "Description[is]": "Reikna einföld reikningsdæmi", + "Description[it]": "Calcola semplici somme", + "Description[ja]": "簡単な計算ができます", + "Description[ka]": "მარტივი ჯამების გამოთვლა", + "Description[ko]": "간단한 계산기", + "Description[lt]": "Paprasti skaičiavimai", + "Description[lv]": "Veiciet vienkārÅ¡as darbÄ«bas", + "Description[nl]": "Eenvoudige berekeningen", + "Description[nn]": "Grafisk kalkulator", + "Description[pl]": "Oblicza proste sumy", + "Description[pt]": "Calcular somas simples", + "Description[pt_BR]": "Calcula funções matemáticas básicas", + "Description[ro]": "Calculează sume simple", + "Description[ru]": "Вычисление простых выражений", + "Description[sk]": "Počítajte jednoduché sumy", + "Description[sl]": "Izračunaj enostavne vsote", + "Description[sv]": "Beräkna enkla summor", + "Description[tr]": "Yalın toplamları hesapla", + "Description[uk]": "Обчисліть прості суми", + "Description[vi]": "Tính các phép tính đơn giản", + "Description[x-test]": "xxCalculate simple sumsxx", + "Description[zh_CN]": "简易四则运算计算器", + "Description[zh_TW]": "做些基本計算", + "Icon": "accessories-calculator", + "Id": "org.kde.plasma.calculator", + "License": "GPL-2.0+", + "Name": "Calculator", + "Name[ar]": "آلة حاسبة", + "Name[az]": "Kalkulyator", + "Name[bg]": "Калкулатор", + "Name[ca@valencia]": "Calculadora", + "Name[ca]": "Calculadora", + "Name[cs]": "Kalkulačka", + "Name[da]": "Lommeregner", + "Name[de]": "Rechner", + "Name[en_GB]": "Calculator", + "Name[eo]": "Kalkulilo", + "Name[es]": "Calculadora", + "Name[eu]": "Kalkulagailua", + "Name[fi]": "Laskin", + "Name[fr]": "Calculatrice", + "Name[gl]": "Calculadora", + "Name[he]": "מחשבון", + "Name[hu]": "Számológép", + "Name[ia]": "Calculator", + "Name[id]": "Kalkulator", + "Name[is]": "Reiknivél", + "Name[it]": "Calcolatrice", + "Name[ja]": "計算機", + "Name[ka]": "კალკულატორი", + "Name[ko]": "계산기", + "Name[lt]": "Skaičiuotuvas", + "Name[lv]": "Kalkulators", + "Name[nl]": "Rekenmachine", + "Name[nn]": "Kalkulator", + "Name[pl]": "Kalkulator", + "Name[pt]": "Calculadora", + "Name[pt_BR]": "Calculadora", + "Name[ro]": "Calculator", + "Name[ru]": "Калькулятор", + "Name[sk]": "Kalkulačka", + "Name[sl]": "Kalkulator", + "Name[sv]": "Räknare", + "Name[tr]": "Hesap Makinesi", + "Name[uk]": "Калькулятор", + "Name[vi]": "Máy tính bỏ túi", + "Name[x-test]": "xxCalculatorxx", + "Name[zh_CN]": "计算器", + "Name[zh_TW]": "計算機", + "Website": "https://kde.org/plasma-desktop/" + }, + "X-Plasma-API-Minimum-Version": "6.0" +} diff --git a/applets/colorpicker/CMakeLists.txt b/applets/colorpicker/CMakeLists.txt new file mode 100644 index 00000000..b27ec003 --- /dev/null +++ b/applets/colorpicker/CMakeLists.txt @@ -0,0 +1,10 @@ +plasma_install_package(package org.kde.plasma.colorpicker) + +ecm_add_qml_module(colorpickerplugin URI org.kde.plasma.private.colorpicker) + +target_sources(colorpickerplugin PRIVATE + plugin/grabwidget.cpp + plugin/colorpickerplugin.cpp +) +target_link_libraries(colorpickerplugin PRIVATE Qt::DBus Qt::Gui Qt::Qml Qt::Widgets) +ecm_finalize_qml_module(colorpickerplugin) diff --git a/applets/colorpicker/Messages.sh b/applets/colorpicker/Messages.sh new file mode 100755 index 00000000..e97f1fc2 --- /dev/null +++ b/applets/colorpicker/Messages.sh @@ -0,0 +1,3 @@ +#! /usr/bin/env bash +$XGETTEXT `find . -name \*.qml -o -name \*.js` -o $podir/plasma_applet_org.kde.plasma.colorpicker.pot +rm -f rc.cpp diff --git a/applets/colorpicker/package/contents/config/config.qml b/applets/colorpicker/package/contents/config/config.qml new file mode 100644 index 00000000..c21fb0f4 --- /dev/null +++ b/applets/colorpicker/package/contents/config/config.qml @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2015 Kai Uwe Broulik + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + */ + +import org.kde.plasma.configuration + +ConfigModel { + ConfigCategory { + name: i18nc("@title", "General") + icon: "preferences-desktop-plasma" + source: "configGeneral.qml" + } +} diff --git a/applets/colorpicker/package/contents/config/main.xml b/applets/colorpicker/package/contents/config/main.xml new file mode 100644 index 00000000..c8238f58 --- /dev/null +++ b/applets/colorpicker/package/contents/config/main.xml @@ -0,0 +1,26 @@ + + + + + + + + + + true + + + #RRGGBB + + + true + + + 1 + + + + diff --git a/applets/colorpicker/package/contents/ui/ColorCircle.qml b/applets/colorpicker/package/contents/ui/ColorCircle.qml new file mode 100644 index 00000000..db3f712f --- /dev/null +++ b/applets/colorpicker/package/contents/ui/ColorCircle.qml @@ -0,0 +1,109 @@ +/* + SPDX-FileCopyrightText: 2015 Kai Uwe Broulik + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +import QtQuick + +import org.kde.plasma.core as PlasmaCore +import org.kde.kirigami as Kirigami +import org.kde.plasma.components as PlasmaComponents3 +import org.kde.plasma.plasmoid + +import "logic.js" as Logic + +PlasmaComponents3.ToolButton { + id: colorButton + + property alias color: colorCircle.color + property alias colorCircle: colorCircle + property Item loadingIndicator: null + + // indicate viable drag... + checked: (dropArea.containsAcceptableDrag && index === 0) || colorButton.pressed || dragHandler.active + checkable: checked + display: PlasmaComponents3.AbstractButton.IconOnly + text: Logic.formatColor(colorCircle.color, root.defaultFormat) + + Drag.dragType: Drag.Automatic + Drag.supportedActions: Qt.CopyAction + Drag.mimeData: { + "application/x-color": colorCircle.color, + "text/plain": colorButton.text + } + + onClicked: root.expanded = !root.expanded + + PlasmaCore.ToolTipArea { + anchors.fill: parent + mainText: colorButton.text + subText: i18nc("@info:usagetip", "Middle-click to copy the color code") + } + + Rectangle { + id: colorCircle + anchors.centerIn: parent + // try to match the color-picker icon in size + width: Kirigami.Units.iconSizes.roundedIconSize(pickerIcon.width) * 0.75 + height: Kirigami.Units.iconSizes.roundedIconSize(pickerIcon.height) * 0.75 + radius: width / 2 + + function luminance(color) { + if (!color) { + return 0; + } + + // formula for luminance according to https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef + + const a = [color.r, color.g, color.b].map(v => + (v <= 0.03928) ? v / 12.92 : Math.pow(((v + 0.055) / 1.055), 2.4 )); + + return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722; + } + + border { + color: Kirigami.Theme.textColor + width: { + const contrast = luminance(Kirigami.Theme.viewBackgroundColor) / luminance(colorCircle.color) + 0.05; + + // show border only if there's too little contrast to the surrounding view or color is transparent + if (contrast > 3 && colorCircle.color.a > 0.5) { + return 0; + } else { + return Math.round(Math.max(1, width / 20)); + } + } + } + + DragHandler { + id: dragHandler + + enabled: historyModel.count > 0 + + onActiveChanged: if (active) { + colorCircle.grabToImage((result) => { + colorButton.Drag.imageSource = result.url; + colorButton.Drag.active = dragHandler.active; + }); + } else { + colorButton.Drag.active = false; + colorButton.Drag.imageSource = ""; + } + } + + TapHandler { + acceptedButtons: Qt.LeftButton + onTapped: (eventPoint, button) => { + colorButton.clicked(); + } + } + + TapHandler { + acceptedButtons: Qt.MiddleButton + onTapped: (eventPoint, button) => { + picker.copyToClipboard(colorButton.text); + } + } + } +} diff --git a/applets/colorpicker/package/contents/ui/ColorContextMenu.qml b/applets/colorpicker/package/contents/ui/ColorContextMenu.qml new file mode 100644 index 00000000..e94852b6 --- /dev/null +++ b/applets/colorpicker/package/contents/ui/ColorContextMenu.qml @@ -0,0 +1,36 @@ +/* + SPDX-FileCopyrightText: 2015 Kai Uwe Broulik + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +import QtQuick +import org.kde.plasma.core as PlasmaCore +import org.kde.plasma.extras as PlasmaExtras + +/** + * Context menu to copy colors in different formats. + */ +PlasmaExtras.ModelContextMenu { + id: formattingMenu + + required property QtObject picker + required property Item colorLabel + required property Item copyIndicatorLabel + required property Timer colorLabelRestoreTimer + + placement: PlasmaExtras.Menu.BottomPosedLeftAlignedPopup + + onClicked: { + picker.copyToClipboard(model.text) + colorLabel.visible = false; + copyIndicatorLabel.visible = true; + colorLabelRestoreTimer.start() + } + + onStatusChanged: { + if (status === PlasmaExtras.Menu.Closed) { + formattingMenu.destroy(); + } + } +} diff --git a/applets/colorpicker/package/contents/ui/CompactRepresentation.qml b/applets/colorpicker/package/contents/ui/CompactRepresentation.qml new file mode 100644 index 00000000..ba2e3a64 --- /dev/null +++ b/applets/colorpicker/package/contents/ui/CompactRepresentation.qml @@ -0,0 +1,128 @@ +/* + SPDX-FileCopyrightText: 2015 Kai Uwe Broulik + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +import QtQuick +import QtQuick.Layouts + +import org.kde.kwindowsystem as KWindowSystem +import org.kde.plasma.core as PlasmaCore +import org.kde.kirigami as Kirigami +import org.kde.plasma.components as PlasmaComponents3 +import org.kde.plasma.plasmoid +import org.kde.plasma.private.colorpicker as ColorPicker + +import "logic.js" as Logic + +DropArea { + id: dropArea + + Layout.minimumWidth: root.isVertical ? Kirigami.Units.iconSizes.small : (grid.width + spacer.implicitWidth) + Layout.minimumHeight: root.isVertical ? (grid.height + spacer.implicitHeight) : Kirigami.Units.iconSizes.small + Layout.preferredWidth: Layout.minimumWidth + Layout.preferredHeight: Layout.minimumHeight + + readonly property real buttonSize: root.isVertical ? parent.width : parent.height + property bool containsAcceptableDrag: false + + onEntered: drag => { + containsAcceptableDrag = (drag.hasColor || drag.hasUrls || ColorPicker.Utils.isValidColor(drag.text)); + } + onExited: containsAcceptableDrag = false + onDropped: drop => { + if (drop.hasColor) { + addColorToHistory(drop.colorData) + } else if (ColorPicker.Utils.isValidColor(drop.text)) { + addColorToHistory(drop.text) + } else if (drop.hasUrls) { + // Show loading indicator above the pick button if there are more than 1 circle + const indicatorParent = circleRepeater.count === 1 ? circleRepeater.itemAt(0) : pickColorButton; + Logic.showLoadingIndicator(indicatorParent, drop.urls); + const component = Qt.createComponent("ImageColors.qml"); + drop.urls.forEach(path => { + component.incubateObject(indicatorParent, { + "source": path, + "loadingIndicator": indicatorParent.loadingIndicator, + }, Qt.Asynchronous); + }); + component.destroy(); + } + containsAcceptableDrag = false + } + + GridLayout { + id: grid + + width: root.isVertical ? dropArea.buttonSize : implicitWidth + height: root.isVertical ? implicitHeight : dropArea.buttonSize + + anchors.centerIn: parent + columns: root.isVertical ? 1 : (1 + (circleRepeater.count > 0 ? circleRepeater.count + 1 : 0)) + rows: root.isVertical ? (1 + (circleRepeater.count > 0 ? circleRepeater.count + 1 : 0)) : 1 + rowSpacing: 0 + columnSpacing: 0 + + PlasmaComponents3.ToolButton { + id: pickColorButton + + Layout.preferredWidth: dropArea.buttonSize + Layout.preferredHeight: dropArea.buttonSize + + property Item loadingIndicator: null + + display: PlasmaComponents3.AbstractButton.IconOnly + enabled: KWindowSystem.KWindowSystem.isPlatformWayland || KWindowSystem.KX11Extras.compositingActive + text: i18nc("@info:tooltip", "Pick color") + + onClicked: root.pickColor() + + PlasmaCore.ToolTipArea { + anchors.fill: parent + mainText: parent.text + subText: xi18nc("@info:usagetip", "Drag a color code here to save itDrag an image file here to get its average color"); + } + + Kirigami.Icon { + id: pickerIcon + anchors.centerIn: parent + width: Math.round(parent.width * 0.9) + height: width + source: "color-picker" + active: parent.hovered + } + } + + Item { // spacer + id: spacer + + readonly property real thickness: dropArea.buttonSize / Kirigami.Units.iconSizes.small + + Layout.preferredWidth: root.isVertical ? parent.width : thickness + Layout.preferredHeight: root.isVertical ? thickness : parent.height + visible: circleRepeater.count > 0 + + Rectangle { + anchors.centerIn: parent + width: circleRepeater.count > 0 ? Math.min(parent.width, circleRepeater.itemAt(0).colorCircle.width) : 0 + height: circleRepeater.count > 0 ? Math.min(parent.height, circleRepeater.itemAt(0).colorCircle.height) : 0 + color: Kirigami.Theme.textColor + opacity: 0.6 + } + } + + Repeater { + id: circleRepeater + + model: Plasmoid.configuration.compactPreviewCount + + delegate: ColorCircle { + Layout.preferredWidth: dropArea.buttonSize + Layout.preferredHeight: dropArea.buttonSize + + color: historyModel.count > index ? historyModel.get(index).color : "#00000000" // transparent as fallback + } + } + } +} diff --git a/applets/colorpicker/package/contents/ui/ImageColors.qml b/applets/colorpicker/package/contents/ui/ImageColors.qml new file mode 100644 index 00000000..e32d252e --- /dev/null +++ b/applets/colorpicker/package/contents/ui/ImageColors.qml @@ -0,0 +1,23 @@ +/* + SPDX-FileCopyrightText: 2022 Fushan Wen + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +import QtQuick + +import org.kde.kirigami as Kirigami + +Kirigami.ImageColors { + id: imageColors + + required property Item loadingIndicator + + onPaletteChanged: { + if (imageColors.palette.length > 0) { + root.addColorToHistory(imageColors.average); + } + loadingIndicator.jobDone(); + imageColors.destroy(); + } +} diff --git a/applets/colorpicker/package/contents/ui/LoadingIndicator.qml b/applets/colorpicker/package/contents/ui/LoadingIndicator.qml new file mode 100644 index 00000000..86561d6a --- /dev/null +++ b/applets/colorpicker/package/contents/ui/LoadingIndicator.qml @@ -0,0 +1,53 @@ +/* + SPDX-FileCopyrightText: 2022 Fushan Wen + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +import QtQuick + +import org.kde.kirigami as Kirigami +import org.kde.plasma.components as PlasmaComponents3 + +/** + * Visible when an image is being processed by \Kirigami.ImageColors + */ +PlasmaComponents3.BusyIndicator { + id: loadingIndicator + anchors.fill: parent + + property int jobRemaining: 0 + + signal jobDone + + onJobDone: { + jobRemaining -= 1; + if (!jobRemaining) { + animator.start(); + } + } + + onJobRemainingChanged: selfDestructionTimer.restart(); + + OpacityAnimator { + id: animator + easing.type: Easing.InQuad + from: 1 + to: 0 + target: loadingIndicator + duration: Kirigami.Units.longDuration + + onStopped: selfDestructionTimer.triggered(); + } + + Timer { + id: selfDestructionTimer + interval: 60000 + running: true + + onTriggered: { + loadingIndicator.parent.loadingIndicator = null; + loadingIndicator.destroy(); + } + } +} diff --git a/applets/colorpicker/package/contents/ui/configGeneral.qml b/applets/colorpicker/package/contents/ui/configGeneral.qml new file mode 100644 index 00000000..d327696a --- /dev/null +++ b/applets/colorpicker/package/contents/ui/configGeneral.qml @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2015 Kai Uwe Broulik + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + */ + +import QtQuick +import QtQuick.Controls as QQC2 +import org.kde.kirigami as Kirigami +import org.kde.kcmutils as KCM + +import "logic.js" as Logic + +KCM.SimpleKCM { + property alias cfg_autoClipboard: autoClipboardCheckBox.checked + property alias cfg_compactPreviewCount: compactPreviewCountSpinBox.value + property string cfg_defaultFormat + property bool cfg_pickOnActivate + + Kirigami.FormLayout { + QQC2.ComboBox { + id: defaultFormatCombo + Kirigami.FormData.label: i18nc("@label:listbox", "Default color format:") + model: Logic.formats + currentIndex: model.indexOf(cfg_defaultFormat) + onActivated: index => { + cfg_defaultFormat = model[index]; + } + } + + QQC2.CheckBox { + id: autoClipboardCheckBox + text: i18nc("@option:check", "Automatically copy color to clipboard") + } + + Item { + Kirigami.FormData.isSection: true + } + + QQC2.RadioButton { + Kirigami.FormData.label: i18nc("@label", "When pressing the keyboard shortcut:") + text: i18nc("@option:radio", "Pick a color") + checked: cfg_pickOnActivate + onToggled: cfg_pickOnActivate = checked + } + + QQC2.RadioButton { + text: i18nc("@option:radio", "Show history") + checked: !cfg_pickOnActivate + } + + Item { + Kirigami.FormData.isSection: true + } + + QQC2.SpinBox { + id: compactPreviewCountSpinBox + Kirigami.FormData.label: i18nc("@label", "Preview count:") + from: 0 + to: 9 /* root.maxColorCount */ + } + } +} diff --git a/applets/colorpicker/package/contents/ui/logic.js b/applets/colorpicker/package/contents/ui/logic.js new file mode 100644 index 00000000..d5b0064e --- /dev/null +++ b/applets/colorpicker/package/contents/ui/logic.js @@ -0,0 +1,100 @@ +/* + * SPDX-FileCopyrightText: 2015 Kai Uwe Broulik + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +.pragma library + +var copyToClipboardText = "Copy to Clipboard"; + +var formats = [ + "RRR,GGG,BBB", + "#rrggbb", + "rrggbb", + "#RRGGBB", + "RRGGBB", + "rgb", + "rgba", + "Qt.rgba", + "LaTeX" +] + +function formatColor(color, format) { + var hexR = padHex((Math.round(color.r * 255)).toString(16)) + var hexG = padHex((Math.round(color.g * 255)).toString(16)) + var hexB = padHex((Math.round(color.b * 255)).toString(16)) + + switch (format) { + case "RRR,GGG,BBB": + return [Math.round(color.r * 255), Math.round(color.g * 255), Math.round(color.b * 255)].join(", ") + case "#rrggbb": + return "#" + formatColor(color, "rrggbb") + case "rrggbb": + return formatColor(color, "RRGGBB").toLowerCase() + case "#RRGGBB": + return "#" + formatColor(color, "RRGGBB") + case "RRGGBB": + return (hexR + hexG + hexB).toUpperCase() + case "rgb": + return "rgb(" + formatColor(color, "RRR,GGG,BBB") + ")" + case "rgba": + return "rgba(" + formatColor(color, "RRR,GGG,BBB") + ", 1)" + case "Qt.rgba": + return "Qt.rgba(" + [roundComponent(color.r), roundComponent(color.g), roundComponent(color.b)].join(", ") + ", 1)" + case "LaTeX": + return "\\definecolor{ColorName}{rgb}{" + [roundComponent(color.r), roundComponent(color.g), roundComponent(color.b)].join(",") + "}" + } +} + +function padHex(n) { + return ("0" + n).substr(-2, 2) // fancy +} + +function roundComponent(n) { + return Math.round(n * 100) / 100 +} + +function menuForColor(color) { + return [ + {text: copyToClipboardText, section: true}, + {text: formatColor(color, "RRR,GGG,BBB")}, + {text: formatColor(color, "rgb")}, + {text: formatColor(color, "rgba")}, + {separator: true}, + {text: formatColor(color, "#rrggbb")}, + {text: formatColor(color, "rrggbb")}, + {text: formatColor(color, "#RRGGBB")}, + {text: formatColor(color, "RRGGBB")}, + {separator: true}, + {text: formatColor(color, "Qt.rgba")}, + {text: formatColor(color, "LaTeX")} + ] +} + +function createContextMenu(visualParent, currentColor, picker, colorLabel, copyIndicatorLabel, colorLabelRestoreTimer) { + const initialArgs = { + model: menuForColor(currentColor), + visualParent, + picker, + colorLabel, + copyIndicatorLabel, + colorLabelRestoreTimer, + }; + const component = Qt.createComponent("ColorContextMenu.qml"); + const menu = component.createObject(visualParent, initialArgs); + component.destroy(); + return menu; +} + +function showLoadingIndicator(parent, urls) { + if (parent.loadingIndicator === null) { + const component = Qt.createComponent(Qt.resolvedUrl("LoadingIndicator.qml")); + parent.loadingIndicator = component.createObject(parent, { + "jobRemaining": urls.length, + }); + component.destroy(); + } else { + parent.loadingIndicator.jobRemaining += urls.length; + } +} diff --git a/applets/colorpicker/package/contents/ui/main.qml b/applets/colorpicker/package/contents/ui/main.qml new file mode 100644 index 00000000..f3e184c0 --- /dev/null +++ b/applets/colorpicker/package/contents/ui/main.qml @@ -0,0 +1,408 @@ +/* + * SPDX-FileCopyrightText: 2015 Kai Uwe Broulik + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick +import QtQuick.Controls as QQC2 +import QtQuick.Layouts +import QtQuick.Dialogs as QtDialogs + +import org.kde.plasma.plasmoid +import org.kde.plasma.core as PlasmaCore +import org.kde.plasma.components as PlasmaComponents3 +import org.kde.plasma.extras as PlasmaExtras +import org.kde.kirigami as Kirigami +import org.kde.kcmutils as KCMUtils +import org.kde.config as KConfig +import org.kde.kwindowsystem + +import org.kde.plasma.private.colorpicker as ColorPicker +import "logic.js" as Logic + +PlasmoidItem { + id: root + + readonly property bool isVertical: Plasmoid.formFactor === PlasmaCore.Types.Vertical + + readonly property color recentColor: historyModel.count > 0 ? historyModel.get(0).color : "#00000000" // transparent as fallback + readonly property string defaultFormat: Plasmoid.configuration.defaultFormat + readonly property int maxColorCount: 9 + + preferredRepresentation: compactRepresentation + + function addColorToHistory(color) { + // this .toString() is needed otherwise Qt completely screws it up + // replacing *all* items in the list by the new items and other nonsense + historyModel.insert(0, { color: color.toString() }); + // limit to 9 entries + if (historyModel.count > maxColorCount) { + historyModel.remove(maxColorCount); + } + historyModel.save(); + } + + function colorPicked(color) { + if (color != recentColor) { + addColorToHistory(color) + } + + if (Plasmoid.configuration.autoClipboard) { + picker.copyToClipboard(Logic.formatColor(color, root.defaultFormat)) + } + } + + function pickColor() { + root.expanded = false + picker.pick() + } + + ColorPicker.GrabWidget { + id: picker + onCurrentColorChanged: colorPicked(currentColor) + } + + Component { + id: colorWindowComponent + + Window { // QTBUG-119055 + id: window + width: Kirigami.Units.gridUnit * 19 + height: Kirigami.Units.gridUnit * 23 + maximumWidth: width + maximumHeight: height + minimumWidth: width + minimumHeight: height + visible: true + title: Plasmoid.title + QtDialogs.ColorDialog { + id: colorDialog + title: Plasmoid.title + selectedColor: historyModel.count > 0 ? root.recentColor : undefined /* Prevent transparent colors */ + onAccepted: { + root.colorPicked(selectedColor); + window.destroy(); + } + onRejected: window.destroy() + } + onClosing: destroy() + Component.onCompleted: colorDialog.open() + } + } + + // prevents the popup from actually opening, needs to be queued + Timer { + id: delayedPickTimer + interval: 0 + onTriggered: root.pickColor() + } + + ListModel { + id: historyModel + + function save() { + let history = []; + for (let i = 0; i < count; i++) { + history.push(get(i).color); + } + Plasmoid.configuration.history = history; + } + } + + Plasmoid.onActivated: { + if (Plasmoid.configuration.pickOnActivate) { + delayedPickTimer.start(); + } + } + + Plasmoid.contextualActions: [ + PlasmaCore.Action { + text: i18nc("@action", "Open Color Dialog") + icon.name: "color-management" + onTriggered: colorWindowComponent.createObject(root) + }, + PlasmaCore.Action { + text: i18nc("@action", "Clear History") + icon.name: "edit-clear-history" + onTriggered: { + historyModel.clear(); + historyModel.save(); + } + }, + PlasmaCore.Action { + text: i18nc("@action", "View History") + icon.name: "view-history" + onTriggered: root.expanded = true + } + ] + + Component.onCompleted: { + Plasmoid.configuration.history.forEach(item => historyModel.append({ color: item })); + Logic.copyToClipboardText = i18ndc("plasma_applet_org.kde.plasma.colorpicker", "@title:menu", "Copy to Clipboard"); // i18n is not supported in js library + } + + compactRepresentation: CompactRepresentation { } + + fullRepresentation: GridView { + id: fullRoot + + readonly property int columns: 3 + + Layout.minimumWidth: columns * Kirigami.Units.gridUnit * 6 + Layout.minimumHeight: Layout.minimumWidth + Layout.maximumWidth: Layout.minimumWidth + Layout.maximumHeight: Layout.minimumHeight + + cellWidth: Math.floor(fullRoot.width / fullRoot.columns) + cellHeight: cellWidth + boundsBehavior: Flickable.StopAtBounds + + model: historyModel + + highlight: PlasmaExtras.Highlight {} + highlightMoveDuration: 0 + + Loader { + width: parent.width - Kirigami.Units.gridUnit * 2 + anchors.centerIn: parent + visible: active + + active: fullRoot.count === 0 && root.expanded + asynchronous: true + + sourceComponent: PlasmaExtras.PlaceholderMessage { + id: emptyHint + + opacity: 0 + iconName: "edit-none" + + readonly property bool compositingActive: KWindowSystem.isPlatformWayland || KX11Extras.compositingActive + + text: compositingActive ? i18nc("@info:usagetip", "No colors") : i18nc("@info:status when color picking is unavailable", "Color picking unavailable when compositing is disabled") + explanation: compositingActive ? "" : i18nc("@info:status when color pick is unavailable", "Compositing has been manually disabled or blocked by a running application") + + helpfulAction: compositingActive ? pickColorAction : enableCompositingAction + + QQC2.Action { + id: pickColorAction + icon.name: "color-picker" + text: i18nc("@action:button", "Pick Color") + onTriggered: root.pickColor() + } + + QQC2.Action { + id: enableCompositingAction + enabled: KConfig.KAuthorized.authorizeControlModule("kwincompositing") + icon.name: "settings-configure" + text: i18nc("@action:button open kwincompositing KCM", "Configure Compositing") + onTriggered: KCMUtils.KCMLauncher.openSystemSettings("kwincompositing") + } + + NumberAnimation { + duration: Kirigami.Units.longDuration + easing.type: Easing.OutCubic + property: "opacity" + running: true + target: emptyHint + to: 1 + } + } + } + + Connections { + target: root + function onExpandedChanged() { + if (root.expanded) { + fullRoot.forceActiveFocus() + } + } + } + + Keys.onPressed: event => { + if (event.key === Qt.Key_Escape) { + root.expanded = false; + event.accepted = true; + } + } + + // This item serves as a drag pixmap and is captured when a drag starts + Rectangle { + id: dragImageDummy + border { + color: Kirigami.Theme.textColor + width: 1 + } + radius: width + width: Kirigami.Units.iconSizes.large + height: Kirigami.Units.iconSizes.large + visible: false + } + + delegate: MouseArea { + id: delegateMouse + + readonly property color currentColor: model.color + + width: fullRoot.cellWidth + height: fullRoot.cellHeight + + drag.target: rect + Drag.dragType: Drag.Automatic + Drag.active: delegateMouse.drag.active + Drag.mimeData: { + "application/x-color": rect.color, + "text/plain": colorLabel.text + } + + acceptedButtons: Qt.LeftButton | Qt.RightButton + hoverEnabled: true + + Keys.onDeletePressed: event => remove() + Keys.onPressed: event => { + switch (event.key) { + case Qt.Key_Space: + case Qt.Key_Enter: + case Qt.Key_Return: + case Qt.Key_Select: + copy(); + break; + case Qt.Key_Menu: + openMenu(); + break; + } + } + + Accessible.name: colorLabel.text + Accessible.role: Accessible.ButtonMenu + + onContainsMouseChanged: { + if (containsMouse) { + fullRoot.currentIndex = index; + } else if (fullRoot.currentIndex === index) { + fullRoot.currentIndex = -1; + } + } + + onPressed: mouse => { + // grab pixmap only once + if (Drag.imageSource.toString() === "") { // cannot just do !Drag.imageSource on QUrl + dragImageDummy.color = currentColor; + dragImageDummy.grabToImage(result => { + Drag.imageSource = result.url; + }); + } + } + + onClicked: mouse => { + if (mouse.button === Qt.LeftButton) { + copy(); + } else if (mouse.button === Qt.RightButton) { + openMenu(); + } + } + + function copy() { + picker.copyToClipboard(Logic.formatColor(currentColor, root.defaultFormat)) + colorLabel.visible = false; + copyIndicatorLabel.visible = true; + colorLabelRestoreTimer.start() + } + + function openMenu() { + const menu = Logic.createContextMenu(this, currentColor, picker, colorLabel, copyIndicatorLabel, colorLabelRestoreTimer); + menu.openRelative(); + } + + function remove() { + historyModel.remove(index); + historyModel.save(); + } + + PlasmaCore.ToolTipArea { + anchors.fill: parent + active: colorLabel.truncated + mainText: colorLabel.text + } + + Timer { + id: colorLabelRestoreTimer + interval: Kirigami.Units.humanMoment + onTriggered: { + colorLabel.visible = true; + copyIndicatorLabel.visible = false; + } + } + + Rectangle { + id: rect + + anchors { + fill: parent + margins: Kirigami.Units.smallSpacing + } + + color: delegateMouse.currentColor + + border { + color: Kirigami.Theme.textColor + width: 1 + } + + Rectangle { + anchors { + bottom: parent.bottom + left: parent.left + right: parent.right + margins: rect.border.width + } + height: colorLabel.contentHeight + 2 * Kirigami.Units.smallSpacing + color: Kirigami.Theme.backgroundColor + opacity: 0.8 + + PlasmaComponents3.Label { + id: colorLabel + anchors.fill: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + elide: Text.ElideLeft + fontSizeMode: Text.HorizontalFit + minimumPointSize: Kirigami.Theme.smallFont.pointSize + text: Logic.formatColor(delegateMouse.currentColor, root.defaultFormat) + textFormat: Text.PlainText + } + + PlasmaComponents3.Label { + id: copyIndicatorLabel + visible: false + anchors.fill: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + elide: Text.ElideLeft + fontSizeMode: Text.HorizontalFit + minimumPointSize: Kirigami.Theme.smallFont.pointSize + text: i18nc("@info:progress just copied a color to clipboard", "Copied!") + textFormat: Text.PlainText + } + } + } + + Loader { + active: parent.containsMouse || Kirigami.Settings.tabletMode || Kirigami.Settings.hasTransientTouchInput + anchors.right: parent.right + anchors.top: parent.top + sourceComponent: PlasmaComponents3.Button { + text: i18nc("@action:button", "Delete") + icon.name: "delete" + display: PlasmaComponents3.AbstractButton.IconOnly + + onClicked: delegateMouse.remove() + + PlasmaComponents3.ToolTip { + text: parent.text + } + } + } + } + } +} diff --git a/applets/colorpicker/package/metadata.json b/applets/colorpicker/package/metadata.json new file mode 100644 index 00000000..48d47fe7 --- /dev/null +++ b/applets/colorpicker/package/metadata.json @@ -0,0 +1,149 @@ +{ + "KPackageStructure": "Plasma/Applet", + "KPlugin": { + "Authors": [ + { + "Email": "kde@privat.broulik.de", + "Name": "Kai Uwe Broulik", + "Name[ar]": "كاي أووي بروتيك", + "Name[az]": "Kai Uwe Broulik", + "Name[bg]": "Kai Uwe Broulik", + "Name[ca@valencia]": "Kai Uwe Broulik", + "Name[ca]": "Kai Uwe Broulik", + "Name[cs]": "Kai Uwe Broulik", + "Name[da]": "Kai Uwe Broulik", + "Name[de]": "Kai Uwe Broulik", + "Name[en_GB]": "Kai Uwe Broulik", + "Name[eo]": "Kai Uwe Broulik", + "Name[es]": "Kai Uwe Broulik", + "Name[eu]": "Kai Uwe Broulik", + "Name[fi]": "Kai Uwe Broulik", + "Name[fr]": "Kai Uwe Broulik", + "Name[gl]": "Kai Uwe Broulik", + "Name[he]": "קאי אווה ברוליק", + "Name[hu]": "Kai Uwe Broulik", + "Name[ia]": "Kai Uwe Broulik", + "Name[id]": "Kai Uwe Broulik", + "Name[is]": "Kai Uwe Broulik", + "Name[it]": "Kai Uwe Broulik", + "Name[ja]": "Kai Uwe Broulik", + "Name[ka]": "კაი უვე ბროულიკი", + "Name[ko]": "Kai Uwe Broulik", + "Name[lt]": "Kai Uwe Broulik", + "Name[lv]": "Kai Uwe Broulik", + "Name[nl]": "Kai Uwe Broulik", + "Name[nn]": "Kai Uwe Broulik", + "Name[pl]": "Kai Uwe Broulik", + "Name[pt]": "Kai Uwe Broulik", + "Name[pt_BR]": "Kai Uwe Broulik", + "Name[ro]": "Kai Uwe Broulik", + "Name[ru]": "Kai Uwe Broulik", + "Name[sk]": "Kai Uwe Broulik", + "Name[sl]": "Kai Uwe Broulik", + "Name[sv]": "Kai Uwe Broulik", + "Name[tr]": "Kai Uwe Broulik", + "Name[uk]": "Kai Uwe Broulik", + "Name[vi]": "Kai Uwe Broulik", + "Name[x-test]": "xxKai Uwe Broulikxx", + "Name[zh_CN]": "Kai Uwe Broulik", + "Name[zh_TW]": "Kai Uwe Broulik" + } + ], + "BugReportUrl": "https://bugs.kde.org/enter_bug.cgi?product=kdeplasma-addons&component=Color Picker", + "Category": "Graphics", + "Description": "Pick a color from the desktop", + "Description[ar]": "اختر لونا من سطح المكتب", + "Description[az]": "Rəngi İş masasından seçin", + "Description[bg]": "Избиране на цвят от работния плот", + "Description[ca@valencia]": "Selecciona un color de l'escriptori", + "Description[ca]": "Selecciona un color de l'escriptori", + "Description[cs]": "Vzít barvu z plochy", + "Description[da]": "Vælg en farve fra skrivebordet", + "Description[de]": "Eine Farbe von der Arbeitsfläche wählen", + "Description[en_GB]": "Pick a colour from the desktop", + "Description[eo]": "Elekti koloron el la labortablo", + "Description[es]": "Seleccionar un color del escritorio", + "Description[eu]": "Hartu mahaigaineko kolore bat", + "Description[fi]": "Valitse työpöydältä väri", + "Description[fr]": "Sélectionner une couleur à partir de votre bureau", + "Description[gl]": "Escolle unha cor do escritorio", + "Description[he]": "בחירת צבע משולחן העבודה", + "Description[hu]": "Szín választása az asztalról", + "Description[ia]": "Selige un color ex le scriptorio", + "Description[id]": "Pilih sebuah warna dari desktop", + "Description[is]": "Plokka lit af skjáborði", + "Description[it]": "Ottieni un colore dal desktop", + "Description[ja]": "デスクトップから色を取得します", + "Description[ka]": "აირჩიეთ ფერი სამუშაო მაგიდიდან", + "Description[ko]": "바탕 화면의 색상 가져오기", + "Description[lt]": "Pasirinkti spalvą iÅ¡ darbalaukio", + "Description[lv]": "Nosakiet krāsu uz darbvirsmas", + "Description[nl]": "Kies een kleur van het bureaublad", + "Description[nn]": "Hent fargar frÃ¥ skrivebordet", + "Description[pl]": "Wybiera kolory z pulpitu", + "Description[pt]": "Escolher uma cor do ecrã", + "Description[pt_BR]": "Escolha uma cor na área de trabalho", + "Description[ro]": "Alegeți o culoare de pe birou", + "Description[ru]": "Выбор цвета из точки рабочего стола", + "Description[sk]": "Vyberte farbu z pracovnej plochy", + "Description[sl]": "Izberi barvo iz namizja", + "Description[sv]": "Hämta en färg frÃ¥n skrivbordet", + "Description[tr]": "Masaüstünden bir renk seç", + "Description[uk]": "Вибір кольору зі стільниці", + "Description[vi]": "Nhặt một màu trên bàn làm việc", + "Description[x-test]": "xxPick a color from the desktopxx", + "Description[zh_CN]": "拾取桌面环境中的颜色", + "Description[zh_TW]": "從桌面上提取顏色", + "FormFactors": [ + "desktop" + ], + "Icon": "color-picker", + "Id": "org.kde.plasma.colorpicker", + "License": "GPL-2.0+", + "Name": "Color Picker", + "Name[ar]": "منتق الألوان", + "Name[az]": "Rəng seçici", + "Name[bg]": "Избиране на цвят", + "Name[ca@valencia]": "Selector de color", + "Name[ca]": "Selector de color", + "Name[cs]": "Kapátko", + "Name[da]": "Farvevælger", + "Name[de]": "Farbwähler", + "Name[en_GB]": "Colour Picker", + "Name[eo]": "Koloro Elektilo", + "Name[es]": "Selector de color", + "Name[eu]": "Kolore hautatzailea", + "Name[fi]": "Värivalinta", + "Name[fr]": "Sélecteur de couleurs", + "Name[gl]": "Selector de cores", + "Name[he]": "בורר צבעים", + "Name[hu]": "Színválasztó", + "Name[ia]": "Selector de colores", + "Name[id]": "Pemilih Warna", + "Name[is]": "Litaplokkari", + "Name[it]": "Selettore del colore", + "Name[ja]": "カラーピッカー", + "Name[ka]": "ფერის ამრჩევი", + "Name[ko]": "색상 선택기", + "Name[lt]": "Spalvų parinkiklis", + "Name[lv]": "Krāsas noteicējs", + "Name[nl]": "Kleurenkiezer", + "Name[nn]": "Fargeveljar", + "Name[pl]": "Wybierak barwy", + "Name[pt]": "Selector de Cores", + "Name[pt_BR]": "Seletor de cores", + "Name[ro]": "Selector de culori", + "Name[ru]": "Выбор цвета", + "Name[sk]": "Výber farieb", + "Name[sl]": "Izbirnik barve", + "Name[sv]": "Färgväljare", + "Name[tr]": "Renk Seçicisi", + "Name[uk]": "Піпетка", + "Name[vi]": "Trình nhặt màu", + "Name[x-test]": "xxColor Pickerxx", + "Name[zh_CN]": "拾色器", + "Name[zh_TW]": "取色工具", + "Website": "https://userbase.kde.org/Plasma/ColorPicker" + }, + "X-Plasma-API-Minimum-Version": "6.0" +} diff --git a/applets/colorpicker/plugin/colorpickerplugin.cpp b/applets/colorpicker/plugin/colorpickerplugin.cpp new file mode 100644 index 00000000..ea6461f6 --- /dev/null +++ b/applets/colorpicker/plugin/colorpickerplugin.cpp @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: 2015 Kai Uwe Broulik + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + */ + +#include +#include +#include + +#include "grabwidget.h" + +class Utils : public QObject +{ + Q_OBJECT + +public: + Q_INVOKABLE bool isValidColor(const QString &colorString) const + { + return QColor::isValidColorName(colorString); + } +}; + +class ColorPickerPlugin : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") + +public: + void registerTypes(const char *uri) override + { + qmlRegisterType(uri, 2, 0, "GrabWidget"); + qmlRegisterSingletonType(uri, 2, 0, "Utils", [](QQmlEngine *, QJSEngine *) { + return new Utils(); + }); + } +}; + +#include "colorpickerplugin.moc" diff --git a/applets/colorpicker/plugin/grabwidget.cpp b/applets/colorpicker/plugin/grabwidget.cpp new file mode 100644 index 00000000..8b77e19e --- /dev/null +++ b/applets/colorpicker/plugin/grabwidget.cpp @@ -0,0 +1,79 @@ +/* + * SPDX-FileCopyrightText: 2015 Kai Uwe Broulik + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + */ + +#include "grabwidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +Q_DECLARE_METATYPE(QColor) + +QDBusArgument &operator<<(QDBusArgument &argument, const QColor &color) +{ + argument.beginStructure(); + argument << color.rgba(); + argument.endStructure(); + return argument; +} + +const QDBusArgument &operator>>(const QDBusArgument &argument, QColor &color) +{ + argument.beginStructure(); + QRgb rgba; + argument >> rgba; + argument.endStructure(); + color = QColor::fromRgba(rgba); + return argument; +} + +GrabWidget::GrabWidget(QObject *parent) + : QObject(parent) +{ + qDBusRegisterMetaType(); +} + +QColor GrabWidget::currentColor() const +{ + return m_currentColor; +} + +void GrabWidget::setCurrentColor(const QColor &color) +{ + if (m_currentColor == color) { + return; + } + m_currentColor = color; + + Q_EMIT currentColorChanged(); +} + +void GrabWidget::pick() +{ + QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KWin"), + QStringLiteral("/ColorPicker"), + QStringLiteral("org.kde.kwin.ColorPicker"), + QStringLiteral("pick")); + auto call = QDBusConnection::sessionBus().asyncCall(msg); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this); + connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) { + watcher->deleteLater(); + QDBusPendingReply reply = *watcher; + if (!reply.isError()) { + setCurrentColor(reply.value()); + } + }); +} + +void GrabWidget::copyToClipboard(const QString &text) +{ + QApplication::clipboard()->setText(text); +} diff --git a/applets/colorpicker/plugin/grabwidget.h b/applets/colorpicker/plugin/grabwidget.h new file mode 100644 index 00000000..57d15f95 --- /dev/null +++ b/applets/colorpicker/plugin/grabwidget.h @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: 2015 Kai Uwe Broulik + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + */ + +#ifndef GRABWIDGET_H +#define GRABWIDGET_H + +#include +#include + +class GrabWidget : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QColor currentColor READ currentColor NOTIFY currentColorChanged) + +public: + explicit GrabWidget(QObject *parent = nullptr); + + QColor currentColor() const; + void setCurrentColor(const QColor &color); + + Q_INVOKABLE void pick(); + Q_INVOKABLE void copyToClipboard(const QString &text); + +Q_SIGNALS: + void currentColorChanged(); + +private: + QColor m_currentColor; +}; + +#endif // GRABWIDGET_H diff --git a/applets/comic/CMakeLists.txt b/applets/comic/CMakeLists.txt new file mode 100644 index 00000000..1a48413e --- /dev/null +++ b/applets/comic/CMakeLists.txt @@ -0,0 +1,32 @@ +add_definitions(-DTRANSLATION_DOMAIN=\"plasma_applet_org.kde.plasma.comic\") + +add_subdirectory(engine) + +set(comic_SRCS + comic.cpp + comicmodel.cpp + checknewstrips.cpp + comicdata.cpp + comicinfo.cpp + comicsaver.cpp + stripselector.cpp + activecomicmodel.cpp +) + +kcoreaddons_add_plugin(org.kde.plasma.comic SOURCES ${comic_SRCS} INSTALL_NAMESPACE "plasma/applets") + +target_link_libraries(org.kde.plasma.comic + Qt::Gui + Qt::Widgets + Plasma::Plasma + KF6::I18n + KF6::KIOCore + KF6::KIOWidgets + KF6::Notifications + KF6::XmlGui + plasma_engine_comic +) + +install(FILES comic.knsrc DESTINATION ${KDE_INSTALL_KNSRCDIR}) +plasma_install_package(package org.kde.plasma.comic) + diff --git a/applets/comic/Messages.sh b/applets/comic/Messages.sh new file mode 100755 index 00000000..0a63fa5a --- /dev/null +++ b/applets/comic/Messages.sh @@ -0,0 +1,3 @@ +#! /usr/bin/env bash +$EXTRACTRC *.ui >> rc.cpp +$XGETTEXT *.cpp `find . -name '*.qml'` -o $podir/plasma_applet_org.kde.plasma.comic.pot diff --git a/applets/comic/activecomicmodel.cpp b/applets/comic/activecomicmodel.cpp new file mode 100644 index 00000000..ac72d455 --- /dev/null +++ b/applets/comic/activecomicmodel.cpp @@ -0,0 +1,54 @@ +/* + * SPDX-FileCopyrightText: 2012 Reza Fatahilah Shah + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "activecomicmodel.h" + +ActiveComicModel::ActiveComicModel(QObject *parent) + : QStandardItemModel(0, 1, parent) +{ + connect(this, &ActiveComicModel::modelReset, this, &ActiveComicModel::countChanged); + connect(this, &ActiveComicModel::rowsInserted, this, &ActiveComicModel::countChanged); + connect(this, &ActiveComicModel::rowsRemoved, this, &ActiveComicModel::countChanged); +} + +QHash ActiveComicModel::roleNames() const +{ + auto roleNames = QStandardItemModel::roleNames(); + roleNames.insert(ComicKeyRole, "key"); + roleNames.insert(ComicTitleRole, "title"); + roleNames.insert(ComicIconRole, "icon"); + roleNames.insert(ComicHighlightRole, "highlight"); + + return roleNames; +} + +void ActiveComicModel::addComic(const QString &key, const QString &title, const QIcon &icon, bool highlight) +{ + QList newRow; + QStandardItem *item = new QStandardItem(title); + + item->setData(key, ComicKeyRole); + item->setData(title, ComicTitleRole); + item->setData(icon, ComicIconRole); + item->setData(highlight, ComicHighlightRole); + + newRow << item; + appendRow(newRow); +} + +QVariantHash ActiveComicModel::get(int row) const +{ + QModelIndex idx = index(row, 0); + QVariantHash hash; + + const auto roleNames = this->roleNames(); + hash.reserve(roleNames.size()); + for (auto end = roleNames.constEnd(), it = roleNames.constBegin(); it != end; ++it) { + hash.insert(QString::fromUtf8(it.value()), data(idx, it.key())); + } + + return hash; +} diff --git a/applets/comic/activecomicmodel.h b/applets/comic/activecomicmodel.h new file mode 100644 index 00000000..8b730e77 --- /dev/null +++ b/applets/comic/activecomicmodel.h @@ -0,0 +1,42 @@ +/* + * SPDX-FileCopyrightText: 2012 Reza Fatahilah Shah + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef ACTIVE_COMIC_MODEL_H +#define ACTIVE_COMIC_MODEL_H + +#include + +class ActiveComicModel : public QStandardItemModel +{ + Q_OBJECT + Q_PROPERTY(int count READ count NOTIFY countChanged) + +public: + enum Roles { + ComicKeyRole = Qt::UserRole + 1, + ComicTitleRole = Qt::UserRole + 2, + ComicIconRole = Qt::UserRole + 3, + ComicHighlightRole = Qt::UserRole + 4, + }; + + explicit ActiveComicModel(QObject *parent = nullptr); + + QHash roleNames() const override; + + void addComic(const QString &key, const QString &title, const QIcon &icon, bool highlight = true); + + int count() + { + return rowCount(QModelIndex()); + } + + Q_INVOKABLE QVariantHash get(int i) const; + +Q_SIGNALS: + void countChanged(); +}; + +#endif diff --git a/applets/comic/checknewstrips.cpp b/applets/comic/checknewstrips.cpp new file mode 100644 index 00000000..9c1e0c02 --- /dev/null +++ b/applets/comic/checknewstrips.cpp @@ -0,0 +1,65 @@ +/* + * SPDX-FileCopyrightText: 2011 Matthias Fuchs + * SPDX-FileCopyrightText: 2022 Alexander Lohnau + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "checknewstrips.h" + +#include + +CheckNewStrips::CheckNewStrips(const QStringList &identifiers, ComicEngine *engine, int minutes, QObject *parent) + : QObject(parent) + , mMinutes(minutes) + , mIndex(0) + , mEngine(engine) + , mIdentifiers(identifiers) +{ + QTimer *timer = new QTimer(this); + timer->setInterval(minutes * 60 * 1000); + connect(timer, &QTimer::timeout, this, &CheckNewStrips::start); + timer->start(); + + // start at once, that way the user does not have to wait for minutes to get the initial result + start(); + connect(mEngine, &ComicEngine::requestFinished, this, &CheckNewStrips::dataUpdated); +} + +void CheckNewStrips::dataUpdated(const ComicMetaData &data) +{ + const QString source = data.identifier; + QString lastIdentifierSuffix; + + if (!data.error) { + lastIdentifierSuffix = data.identifier; + lastIdentifierSuffix.remove(source); + } + + if (!lastIdentifierSuffix.isEmpty()) { + QString temp = source; + temp.remove(QLatin1Char(':')); + Q_EMIT lastStrip(mIndex, temp, lastIdentifierSuffix); + } + ++mIndex; + + if (mIndex < mIdentifiers.count()) { + const QString newSource = mIdentifiers[mIndex] + QLatin1Char(':'); + mEngine->requestSource(newSource); + } else { + mIndex = 0; + } +} + +void CheckNewStrips::start() +{ + // already running, do nothing + if (mIndex) { + return; + } + + if (mIndex < mIdentifiers.count()) { + const QString newSource = mIdentifiers[mIndex] + QLatin1Char(':'); + mEngine->requestSource(newSource); + } +} diff --git a/applets/comic/checknewstrips.h b/applets/comic/checknewstrips.h new file mode 100644 index 00000000..93f2bbb1 --- /dev/null +++ b/applets/comic/checknewstrips.h @@ -0,0 +1,44 @@ +/* + * SPDX-FileCopyrightText: 2011 Matthias Fuchs + * SPDX-FileCopyrightText: 2022 Alexander Lohnau + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef CHECK_NEW_STRIPS_H +#define CHECK_NEW_STRIPS_H + +#include "engine/comic.h" + +/** + * This class searches for the newest comic strips of predefined comics in a defined interval. + * Once found it emits lastStrip + */ +class CheckNewStrips : public QObject +{ + Q_OBJECT + +public: + CheckNewStrips(const QStringList &identifiers, ComicEngine *engine, int minutes, QObject *parent = nullptr); + +Q_SIGNALS: + /** + * @param index of the identifier in identifiers + * @param identifier of the comic + * @param suffix of the last comic strip + * @see CheckNewStrips + */ + void lastStrip(int index, const QString &identifier, const QString &suffix); + +private Q_SLOTS: + void start(); + +private: + void dataUpdated(const ComicMetaData &data); + int mMinutes; + int mIndex; + ComicEngine *mEngine; + const QStringList mIdentifiers; +}; + +#endif diff --git a/applets/comic/comic.cpp b/applets/comic/comic.cpp new file mode 100644 index 00000000..827e791b --- /dev/null +++ b/applets/comic/comic.cpp @@ -0,0 +1,741 @@ +/* + * SPDX-FileCopyrightText: 2007 Tobias Koenig + * SPDX-FileCopyrightText: 2008 Marco Martin + * SPDX-FileCopyrightText: 2008-2011 Matthias Fuchs + * SPDX-FileCopyrightText: 2012 Reza Fatahilah Shah + * SPDX-FileCopyrightText: 2015 Marco Martin + * SPDX-FileCopyrightText: 2022 Alexander Lohnau + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "comic.h" +#include "checknewstrips.h" +#include "comic_debug.h" +#include "comicsaver.h" +#include "stripselector.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "comicmodel.h" + +ComicApplet::ComicApplet(QObject *parent, const KPluginMetaData &data, const QVariantList &args) + : Plasma::Applet(parent, data, args) + , mProxy(nullptr) + , mActiveComicModel(new ActiveComicModel(parent)) + , mDifferentComic(true) + , mShowComicUrl(false) + , mShowComicAuthor(false) + , mShowComicTitle(false) + , mShowComicIdentifier(false) + , mShowErrorPicture(true) + , mArrowsOnHover(true) + , mMiddleClick(true) + , mCheckNewComicStripsInterval(0) + , mMaxComicLimit(0) + , mCheckNewStrips(nullptr) + , mActionShop(nullptr) + , mEngine(new ComicEngine(this)) + , mSavingDir(nullptr) +{ + setHasConfigurationInterface(true); +} + +void ComicApplet::init() +{ + mSavingDir = new SavingDir(config()); + + configChanged(); + + mModel = new ComicModel(mEngine, mTabIdentifier, this); + mProxy = new QSortFilterProxyModel(this); + mProxy->setSourceModel(mModel); + mProxy->setSortCaseSensitivity(Qt::CaseInsensitive); + mProxy->sort(1, Qt::AscendingOrder); + + mCurrentDay = QDate::currentDate(); + mDateChangedTimer = new QTimer(this); + connect(mDateChangedTimer, &QTimer::timeout, this, &ComicApplet::checkDayChanged); + mDateChangedTimer->setInterval(5 * 60 * 1000); // every 5 minutes + mDateChangedTimer->start(); + + mActionNextNewStripTab = new QAction(QIcon::fromTheme(QStringLiteral("go-next-view")), i18nc("@action comic strip", "&Next Tab with a New Strip"), this); + mActionNextNewStripTab->setShortcuts(KStandardShortcut::openNew()); + setInternalAction(QStringLiteral("next new strip"), mActionNextNewStripTab); + mActions.append(mActionNextNewStripTab); + connect(mActionNextNewStripTab, &QAction::triggered, this, &ComicApplet::showNextNewStrip); + + mActionGoFirst = new QAction(QIcon::fromTheme(QStringLiteral("go-first")), i18nc("@action", "Jump to &First Strip"), this); + mActions.append(mActionGoFirst); + connect(mActionGoFirst, &QAction::triggered, this, &ComicApplet::slotFirstDay); + + mActionGoLast = new QAction(QIcon::fromTheme(QStringLiteral("go-last")), i18nc("@action", "Jump to &Current Strip"), this); + mActions.append(mActionGoLast); + connect(mActionGoLast, &QAction::triggered, this, &ComicApplet::slotCurrentDay); + + mActionGoJump = new QAction(QIcon::fromTheme(QStringLiteral("go-jump")), i18nc("@action", "Jump to Strip…"), this); + mActions.append(mActionGoJump); + connect(mActionGoJump, &QAction::triggered, this, &ComicApplet::slotGoJump); + + mActionWebsite = new QAction(i18nc("@action", "Visit the Website"), this); + KService::Ptr browser = KApplicationTrader::preferredService(QStringLiteral("x-scheme-handler/https")); + + if (browser) { + mActionWebsite->setText(i18nc("@action:inmenu %1 is the name of a web browser", "View in %1", browser->name())); + mActionWebsite->setIcon(QIcon::fromTheme(browser->icon())); + } + + mActionWebsite->setEnabled(false); + mActions.append(mActionWebsite); + connect(mActionWebsite, &QAction::triggered, this, &ComicApplet::slotWebsite); + + mActionShop = new QAction(i18nc("@action", "Visit the Shop &Website"), this); + mActionShop->setEnabled(false); + mActions.append(mActionShop); + connect(mActionShop, &QAction::triggered, this, &ComicApplet::slotShop); + + mActionSaveComicAs = new QAction(QIcon::fromTheme(QStringLiteral("document-save-as")), i18nc("@action", "&Save Comic As…"), this); + mActions.append(mActionSaveComicAs); + connect(mActionSaveComicAs, &QAction::triggered, this, &ComicApplet::slotSaveComicAs); + + mActionScaleContent = new QAction(QIcon::fromTheme(QStringLiteral("zoom-original")), // + i18nc("@option:check Context menu of comic image", "&Actual Size"), + this); + mActionScaleContent->setCheckable(true); + mActionScaleContent->setChecked(mCurrent.scaleComic()); + mActions.append(mActionScaleContent); + connect(mActionScaleContent, &QAction::triggered, this, &ComicApplet::slotScaleToContent); + + mActionStorePosition = + new QAction(QIcon::fromTheme(QStringLiteral("go-home")), i18nc("@option:check Context menu of comic image", "Store Current &Position"), this); + mActionStorePosition->setCheckable(true); + mActionStorePosition->setChecked(mCurrent.hasStored()); + mActions.append(mActionStorePosition); + connect(mActionStorePosition, &QAction::triggered, this, &ComicApplet::slotStorePosition); + + // make sure that tabs etc. are displayed even if the comic strip in the first tab does not work + updateView(); + + updateUsedComics(); + changeComic(true); + + connect(mEngine, &ComicEngine::requestFinished, this, &ComicApplet::dataUpdated); + + QNetworkInformation::instance()->loadBackendByFeatures(QNetworkInformation::Feature::Reachability); + connect(QNetworkInformation::instance(), &QNetworkInformation::reachabilityChanged, this, [this](auto reachability) { + if (reachability != QNetworkInformation::Reachability::Online) { + return; + } + qCDebug(PLASMA_COMIC) << "Online status changed to true, requesting comic" << mPreviousFailedIdentifier; + mEngine->requestSource(mPreviousFailedIdentifier); + }); +} + +ComicApplet::~ComicApplet() +{ + delete mSavingDir; +} + +void ComicApplet::dataUpdated(const ComicMetaData &data) +{ + const QString source = data.identifier; + setBusy(false); + + // disconnect prefetched comic strips + if (source != mOldSource) { + return; + } + + setConfigurationRequired(mCurrent.id().isEmpty()); + + // there was an error, display information as image + if (data.error) { + mPreviousFailedIdentifier = source; + if (!mShowErrorPicture && !data.previousIdentifier.isEmpty()) { + updateComic(data.previousIdentifier); + } + return; + } + + mCurrent.setData(data); + + // looking at the last index, thus not mark it as new + KConfigGroup cg = config(); + if (!mCurrent.hasNext() && mCheckNewComicStripsInterval) { + setTabHighlighted(mCurrent.id(), false); + mActionNextNewStripTab->setEnabled(isTabHighlighted(mCurrent.id())); + } + + // call the slot to check if the position needs to be saved + slotStorePosition(); + + // prefetch the previous and following comic for faster navigation + if (mCurrent.hasNext()) { + const QString prefetch = mCurrent.id() + QLatin1Char(':') + mCurrent.next(); + mEngine->requestSource(prefetch); + } + if (mCurrent.hasPrev()) { + const QString prefetch = mCurrent.id() + QLatin1Char(':') + mCurrent.prev(); + mEngine->requestSource(prefetch); + } + + updateView(); + + refreshComicData(); +} + +void ComicApplet::updateView() +{ + updateContextMenu(); +} + +void ComicApplet::positionFullView(QWindow *window) +{ + if (!window || !window->screen()) { + return; + } + + window->setPosition(window->screen()->availableGeometry().center() - QPoint(window->size().width() / 2, window->size().height() / 2)); +} + +void ComicApplet::changeComic(bool differentComic) +{ + if (differentComic) { + KConfigGroup cg = config(); + mActionStorePosition->setChecked(mCurrent.storePosition()); + + // assign mScaleComic the moment the new strip has been loaded (dataUpdated) as up to this point + // the old one should be still shown with its scaling settings + mActionScaleContent->setChecked(mCurrent.scaleComic()); + + updateComic(mCurrent.stored()); + } else { + updateComic(mCurrent.current()); + } +} + +void ComicApplet::updateUsedComics() +{ + const QString oldIdentifier = mCurrent.id(); + + mActiveComicModel->clear(); + mCurrent = ComicData(); + + bool isFirst = true; + QModelIndex data; + KConfigGroup cg = config(); + int tab = 0; + for (int i = 0; i < mProxy->rowCount(); ++i) { + if (mTabIdentifier.contains(mProxy->index(i, 0).data(Qt::UserRole).toString())) { + data = mProxy->index(i, 1); + + if (isFirst) { + isFirst = false; + const QString id = data.data(Qt::UserRole).toString(); + mDifferentComic = (oldIdentifier != id); + const QString title = data.data().toString(); + mCurrent.init(id, config()); + mCurrent.setTitle(title); + } + + const QString name = data.data().toString(); + const QString identifier = data.data(Qt::UserRole).toString(); + const QIcon icon = data.data(Qt::DecorationRole).value(); + // found a newer strip last time, which was not visited + + if (mCheckNewComicStripsInterval && !cg.readEntry(QLatin1String("lastStripVisited_") + identifier, true)) { + mActiveComicModel->addComic(identifier, name, icon, true); + } else { + mActiveComicModel->addComic(identifier, name, icon); + } + + ++tab; + } + } + + mActionNextNewStripTab->setVisible(mCheckNewComicStripsInterval); + mActionNextNewStripTab->setEnabled(isTabHighlighted(mCurrent.id())); + + delete mCheckNewStrips; + mCheckNewStrips = nullptr; + if (mCheckNewComicStripsInterval) { + mCheckNewStrips = new CheckNewStrips(mTabIdentifier, mEngine, mCheckNewComicStripsInterval, this); + connect(mCheckNewStrips, &CheckNewStrips::lastStrip, this, &ComicApplet::slotFoundLastStrip); + } + + Q_EMIT comicModelChanged(); +} + +void ComicApplet::slotTabChanged(const QString &identifier) +{ + bool differentComic = (mCurrent.id() != identifier); + mCurrent = ComicData(); + mCurrent.init(identifier, config()); + changeComic(differentComic); +} + +void ComicApplet::checkDayChanged() +{ + if (mCurrentDay != QDate::currentDate()) { + updateComic(mCurrent.current()); + mCurrentDay = QDate::currentDate(); + } else if (!mCurrent.hasImage()) { + updateComic(mCurrent.stored()); + } +} + +void ComicApplet::configChanged() +{ + KConfigGroup cg = config(); + mTabIdentifier = cg.readEntry("tabIdentifier", QStringList()); + + if (mProxy) { + updateUsedComics(); + } + + const QString id = mTabIdentifier.count() ? mTabIdentifier.at(0) : QString(); + mCurrent = ComicData(); + mCurrent.init(id, cg); + + mShowComicUrl = cg.readEntry("showComicUrl", false); + mShowComicAuthor = cg.readEntry("showComicAuthor", false); + mShowComicTitle = cg.readEntry("showComicTitle", false); + mShowComicIdentifier = cg.readEntry("showComicIdentifier", false); + mShowErrorPicture = cg.readEntry("showErrorPicture", true); + mArrowsOnHover = cg.readEntry("arrowsOnHover", true); + mMiddleClick = cg.readEntry("middleClick", true); + mCheckNewComicStripsInterval = cg.readEntry("checkNewComicStripsIntervall", 30); + + auto oldMaxComicLimit = mMaxComicLimit; + mMaxComicLimit = cg.readEntry("maxComicLimit", 29); + if (oldMaxComicLimit != mMaxComicLimit) { + mEngine->setMaxComicLimit(mMaxComicLimit); + } +} + +void ComicApplet::saveConfig() +{ + KConfigGroup cg = config(); + cg.writeEntry("comic", mCurrent.id()); + cg.writeEntry("showComicUrl", mShowComicUrl); + cg.writeEntry("showComicAuthor", mShowComicAuthor); + cg.writeEntry("showComicTitle", mShowComicTitle); + cg.writeEntry("showComicIdentifier", mShowComicIdentifier); + cg.writeEntry("showErrorPicture", mShowErrorPicture); + cg.writeEntry("arrowsOnHover", mArrowsOnHover); + cg.writeEntry("middleClick", mMiddleClick); + cg.writeEntry("tabIdentifier", mTabIdentifier); + cg.writeEntry("checkNewComicStripsIntervall", mCheckNewComicStripsInterval); + cg.writeEntry("maxComicLimit", mMaxComicLimit); +} + +void ComicApplet::slotNextDay() +{ + updateComic(mCurrent.next()); +} + +void ComicApplet::slotPreviousDay() +{ + updateComic(mCurrent.prev()); +} + +void ComicApplet::slotFirstDay() +{ + updateComic(mCurrent.first()); +} + +void ComicApplet::slotCurrentDay() +{ + updateComic(QString()); +} + +void ComicApplet::slotFoundLastStrip(int index, const QString &identifier, const QString &suffix) +{ + Q_UNUSED(index) + + if (mCurrent.id() != identifier) { + return; + } + + KConfigGroup cg = config(); + if (suffix != cg.readEntry(QLatin1String("lastStrip_") + identifier, QString())) { + qDebug() << identifier << "has a newer strip."; + cg.writeEntry(QLatin1String("lastStripVisited_") + identifier, false); + updateComic(suffix); + } +} + +void ComicApplet::slotGoJump() +{ + StripSelector *selector = StripSelectorFactory::create(mCurrent.type()); + connect(selector, &StripSelector::stripChosen, this, &ComicApplet::updateComic); + + selector->select(mCurrent); +} + +void ComicApplet::slotStorePosition() +{ + mCurrent.storePosition(mActionStorePosition->isChecked()); +} + +void ComicApplet::slotWebsite() +{ + auto *job = new KIO::OpenUrlJob(mCurrent.websiteUrl()); + job->start(); +} + +void ComicApplet::slotShop() +{ + auto *job = new KIO::OpenUrlJob(mCurrent.shopUrl()); + job->start(); +} + +QList ComicApplet::contextualActions() +{ + return mActions; +} + +void ComicApplet::updateComic(const QString &identifierSuffix) +{ + const QString id = mCurrent.id(); + setConfigurationRequired(id.isEmpty()); + + if (!id.isEmpty()) { + setBusy(true); + + const QString identifier = id + QLatin1Char(':') + identifierSuffix; + + // disconnecting of the oldSource is needed, otherwise you could get data for comics you are not looking at if you use tabs + // if there was an error only disconnect the oldSource if it had nothing to do with the error or if the comic changed, that way updates of the error can + // come in + if (!mIdentifierError.isEmpty() && !mIdentifierError.contains(id)) { + mIdentifierError.clear(); + } + mOldSource = identifier; + mEngine->requestSource(identifier); + slotScaleToContent(); + } else { + setBusy(false); + qWarning() << "Either no identifier was specified or the engine could not be created:" + << "id" << id; + } + updateContextMenu(); +} + +void ComicApplet::updateContextMenu() +{ + if (mCurrent.id().isEmpty()) { + mActiveComicModel->clear(); + } + + if (mCurrent.id().isEmpty() || !mCurrent.ready()) { + mActionNextNewStripTab->setEnabled(false); + mActionGoFirst->setEnabled(false); + mActionGoLast->setEnabled(false); + mActionScaleContent->setEnabled(false); + mActionWebsite->setEnabled(false); + mActionShop->setEnabled(false); + mActionStorePosition->setEnabled(false); + mActionGoJump->setEnabled(false); + mActionSaveComicAs->setEnabled(false); + mActionScaleContent->setChecked(false); + } else { + mActionGoFirst->setVisible(mCurrent.hasFirst()); + mActionGoFirst->setEnabled(mCurrent.hasPrev()); + mActionGoLast->setEnabled(true); + mActionWebsite->setEnabled(true); + mActionShop->setEnabled(mCurrent.shopUrl().isValid()); + mActionScaleContent->setEnabled(true); + mActionStorePosition->setEnabled(true); + mActionGoJump->setEnabled(true); + mActionSaveComicAs->setEnabled(true); + } +} + +void ComicApplet::slotSaveComicAs() +{ + ComicSaver saver(mSavingDir); + saver.save(mCurrent); +} + +void ComicApplet::slotScaleToContent() +{ + setShowActualSize(mActionScaleContent->isChecked()); +} + +// QML +QObject *ComicApplet::comicsModel() const +{ + return mActiveComicModel; +} + +QObject *ComicApplet::availableComicsModel() const +{ + return mProxy; +} + +bool ComicApplet::showComicUrl() const +{ + return mShowComicUrl; +} + +void ComicApplet::setShowComicUrl(bool show) +{ + if (show == mShowComicUrl) { + return; + } + + mShowComicUrl = show; + + Q_EMIT showComicUrlChanged(); +} + +bool ComicApplet::showComicAuthor() const +{ + return mShowComicAuthor; +} + +void ComicApplet::setShowComicAuthor(bool show) +{ + if (show == mShowComicAuthor) { + return; + } + + mShowComicAuthor = show; + + Q_EMIT showComicAuthorChanged(); +} + +bool ComicApplet::showComicTitle() const +{ + return mShowComicTitle; +} + +void ComicApplet::setShowComicTitle(bool show) +{ + if (show == mShowComicTitle) { + return; + } + + mShowComicTitle = show; + + Q_EMIT showComicTitleChanged(); +} + +bool ComicApplet::showComicIdentifier() const +{ + return mShowComicIdentifier; +} + +void ComicApplet::setShowComicIdentifier(bool show) +{ + if (show == mShowComicIdentifier) { + return; + } + + mShowComicIdentifier = show; + + Q_EMIT showComicIdentifierChanged(); +} + +bool ComicApplet::showErrorPicture() const +{ + return mShowErrorPicture; +} + +void ComicApplet::setShowErrorPicture(bool show) +{ + if (show == mShowErrorPicture) { + return; + } + + mShowErrorPicture = show; + + Q_EMIT showErrorPictureChanged(); +} + +bool ComicApplet::arrowsOnHover() const +{ + return mArrowsOnHover; +} + +void ComicApplet::setArrowsOnHover(bool show) +{ + if (show == mArrowsOnHover) { + return; + } + + mArrowsOnHover = show; + + Q_EMIT arrowsOnHoverChanged(); +} + +bool ComicApplet::middleClick() const +{ + return mMiddleClick; +} + +void ComicApplet::setMiddleClick(bool show) +{ + if (show == mMiddleClick) { + return; + } + + mMiddleClick = show; + + Q_EMIT middleClickChanged(); + saveConfig(); +} + +QVariantMap ComicApplet::comicData() const +{ + return mComicData; +} + +QStringList ComicApplet::tabIdentifiers() const +{ + return mTabIdentifier; +} + +void ComicApplet::setTabIdentifiers(const QStringList &tabs) +{ + if (mTabIdentifier == tabs) { + return; + } + + mTabIdentifier = tabs; + Q_EMIT tabIdentifiersChanged(); + saveConfig(); + changeComic(mDifferentComic); +} + +void ComicApplet::refreshComicData() +{ + mComicData[QStringLiteral("image")] = mCurrent.image(); + mComicData[QStringLiteral("prev")] = mCurrent.prev(); + mComicData[QStringLiteral("next")] = mCurrent.next(); + mComicData[QStringLiteral("additionalText")] = mCurrent.additionalText(); + + mComicData[QStringLiteral("websiteUrl")] = mCurrent.websiteUrl().toString(); + mComicData[QStringLiteral("websiteHost")] = mCurrent.websiteUrl().host(); + mComicData[QStringLiteral("imageUrl")] = mCurrent.websiteUrl().toString(); + mComicData[QStringLiteral("shopUrl")] = mCurrent.websiteUrl().toString(); + mComicData[QStringLiteral("first")] = mCurrent.first(); + mComicData[QStringLiteral("stripTitle")] = mCurrent.stripTitle(); + mComicData[QStringLiteral("author")] = mCurrent.author(); + mComicData[QStringLiteral("title")] = mCurrent.title(); + + mComicData[QStringLiteral("suffixType")] = QStringLiteral("Date"); + mComicData[QStringLiteral("current")] = mCurrent.current(); + // mComicData[QStringLiteral("last")] = mCurrent.last(); + mComicData[QStringLiteral("currentReadable")] = mCurrent.currentReadable(); + mComicData[QStringLiteral("firstStripNum")] = mCurrent.firstStripNum(); + mComicData[QStringLiteral("maxStripNum")] = mCurrent.maxStripNum(); + mComicData[QStringLiteral("isLeftToRight")] = mCurrent.isLeftToRight(); + mComicData[QStringLiteral("isTopToBottom")] = mCurrent.isTopToBottom(); + + Q_EMIT comicDataChanged(); +} + +bool ComicApplet::showActualSize() const +{ + return mCurrent.scaleComic(); +} + +void ComicApplet::setShowActualSize(bool show) +{ + if (show == mCurrent.scaleComic()) { + return; + } + + mCurrent.setScaleComic(show); + + Q_EMIT showActualSizeChanged(); +} + +int ComicApplet::checkNewComicStripsInterval() const +{ + return mCheckNewComicStripsInterval; +} + +void ComicApplet::setCheckNewComicStripsInterval(int interval) +{ + if (mCheckNewComicStripsInterval == interval) { + return; + } + + mCheckNewComicStripsInterval = interval; + Q_EMIT checkNewComicStripsIntervalChanged(); +} + +void ComicApplet::setMaxComicLimit(int limit) +{ + if (mMaxComicLimit == limit) { + return; + } + + mMaxComicLimit = limit; + Q_EMIT maxComicLimitChanged(); +} + +int ComicApplet::maxComicLimit() const +{ + return mMaxComicLimit; +} + +// Endof QML +void ComicApplet::setTabHighlighted(const QString &id, bool highlight) +{ + // Search for matching id + for (int i = 0; i < mActiveComicModel->rowCount(); ++i) { + QStandardItem *item = mActiveComicModel->item(i); + + QString currentId = item->data(ActiveComicModel::ComicKeyRole).toString(); + if (id == currentId) { + if (highlight != item->data(ActiveComicModel::ComicHighlightRole).toBool()) { + item->setData(highlight, ActiveComicModel::ComicHighlightRole); + Q_EMIT tabHighlightRequest(id, highlight); + } + } + } +} + +bool ComicApplet::isTabHighlighted(const QString &id) const +{ + for (int i = 0; i < mActiveComicModel->rowCount(); ++i) { + QStandardItem *item = mActiveComicModel->item(i); + + QString currentId = item->data(ActiveComicModel::ComicKeyRole).toString(); + if (id == currentId) { + return item->data(ActiveComicModel::ComicHighlightRole).toBool(); + } + } + return false; +} + +void ComicApplet::loadProviders() +{ + mModel->load(); +} + +K_PLUGIN_CLASS(ComicApplet) + +#include "comic.moc" diff --git a/applets/comic/comic.h b/applets/comic/comic.h new file mode 100644 index 00000000..072b1e39 --- /dev/null +++ b/applets/comic/comic.h @@ -0,0 +1,211 @@ +/* + * SPDX-FileCopyrightText: 2007 Tobias Koenig + * SPDX-FileCopyrightText: 2008 Marco Martin + * SPDX-FileCopyrightText: 2008-2010 Matthias Fuchs + * SPDX-FileCopyrightText: 2012 Reza Fatahilah Shah + * SPDX-FileCopyrightText: 2015 Marco Martin + * SPDX-FileCopyrightText: 2022 Alexander Lohnau + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef COMIC_H +#define COMIC_H + +#include +#include + +#include + +#include "activecomicmodel.h" +#include "comicdata.h" +#include "engine/comic.h" + +class CheckNewStrips; +class ComicModel; +class ConfigWidget; +class QAction; +class KJob; +class QAction; +class QSortFilterProxyModel; +class QTimer; +class SavingDir; + +class ComicApplet : public Plasma::Applet +{ + Q_OBJECT + Q_PROPERTY(QObject *comicsModel READ comicsModel NOTIFY comicModelChanged) + Q_PROPERTY(QObject *availableComicsModel READ availableComicsModel CONSTANT) + Q_PROPERTY(bool showComicUrl READ showComicUrl WRITE setShowComicUrl NOTIFY showComicUrlChanged) + Q_PROPERTY(bool showComicAuthor READ showComicAuthor WRITE setShowComicAuthor NOTIFY showComicAuthorChanged) + Q_PROPERTY(bool showComicTitle READ showComicTitle WRITE setShowComicTitle NOTIFY showComicTitleChanged) + Q_PROPERTY(bool showComicIdentifier READ showComicIdentifier WRITE setShowComicIdentifier NOTIFY showComicIdentifierChanged) + Q_PROPERTY(bool showErrorPicture READ showErrorPicture WRITE setShowErrorPicture NOTIFY showErrorPictureChanged) + Q_PROPERTY(bool arrowsOnHover READ arrowsOnHover WRITE setArrowsOnHover NOTIFY arrowsOnHoverChanged) + Q_PROPERTY(bool middleClick READ middleClick WRITE setMiddleClick NOTIFY middleClickChanged) + Q_PROPERTY(QVariantMap comicData READ comicData NOTIFY comicDataChanged) + Q_PROPERTY(bool showActualSize READ showActualSize WRITE setShowActualSize NOTIFY showActualSizeChanged) + Q_PROPERTY(QStringList tabIdentifiers READ tabIdentifiers WRITE setTabIdentifiers NOTIFY tabIdentifiersChanged) + Q_PROPERTY(int checkNewComicStripsInterval READ checkNewComicStripsInterval WRITE setCheckNewComicStripsInterval NOTIFY checkNewComicStripsIntervalChanged) + Q_PROPERTY(int maxComicLimit READ maxComicLimit WRITE setMaxComicLimit NOTIFY maxComicLimitChanged) + +public: + ComicApplet(QObject *parent, const KPluginMetaData &data, const QVariantList &args); + ~ComicApplet() override; + + void init() override; + QList contextualActions() override; + + // For QML + QObject *comicsModel() const; + QObject *availableComicsModel() const; + QVariantMap comicData() const; + + QStringList tabIdentifiers() const; + void setTabIdentifiers(const QStringList &tabs); + + bool showComicUrl() const; + void setShowComicUrl(bool show); + + bool showComicAuthor() const; + void setShowComicAuthor(bool show); + + bool showComicTitle() const; + void setShowComicTitle(bool show); + + bool showComicIdentifier() const; + void setShowComicIdentifier(bool show); + + bool showErrorPicture() const; + void setShowErrorPicture(bool show); + + bool arrowsOnHover() const; + void setArrowsOnHover(bool show); + + bool middleClick() const; + void setMiddleClick(bool show); + + bool showActualSize() const; + void setShowActualSize(bool show); + + int checkNewComicStripsInterval() const; + void setCheckNewComicStripsInterval(int interval); + + void setMaxComicLimit(int limit); + int maxComicLimit() const; + // End for QML + +Q_SIGNALS: + void comicModelChanged(); + void showComicUrlChanged(); + void showComicAuthorChanged(); + void showComicTitleChanged(); + void showComicIdentifierChanged(); + void showErrorPictureChanged(); + void arrowsOnHoverChanged(); + void middleClickChanged(); + void comicDataChanged(); + void tabHighlightRequest(const QString &id, bool highlight); + void showNextNewStrip(); + void showActualSizeChanged(); + void tabIdentifiersChanged(); + void checkNewComicStripsIntervalChanged(); + void maxComicLimitChanged(); + +private Q_SLOTS: + void slotTabChanged(const QString &newIdentifier); + void slotNextDay(); + void slotPreviousDay(); + void slotFirstDay(); + void slotCurrentDay(); + void slotFoundLastStrip(int index, const QString &identifier, const QString &suffix); + void slotGoJump(); + void slotSaveComicAs(); + void slotScaleToContent(); + void slotShop(); + void slotWebsite(); + void slotStorePosition(); + void checkDayChanged(); + +public Q_SLOTS: + void configChanged() override; + void saveConfig(); + Q_INVOKABLE void updateComic(const QString &identifierSuffix = QString()); + + Q_INVOKABLE void goJump() + { + slotGoJump(); + } + + Q_INVOKABLE void shop() + { + slotShop(); + } + + Q_INVOKABLE void tabChanged(const QString &newIdentifier) + { + slotTabChanged(newIdentifier); + } + + Q_INVOKABLE void loadProviders(); + Q_INVOKABLE void positionFullView(QWindow *window); + +private: + void changeComic(bool differentComic); + void updateUsedComics(); + void updateContextMenu(); + void updateView(); + void refreshComicData(); + void setTabHighlighted(const QString &id, bool highlight); + bool isTabHighlighted(const QString &id) const; + void dataUpdated(const ComicMetaData &data); + +private: + ComicModel *mModel; + QString mPreviousFailedIdentifier; + QSortFilterProxyModel *mProxy; + ActiveComicModel *mActiveComicModel; + QVariantMap mComicData; + + QDate mCurrentDay; + + QString mIdentifierError; + QString mOldSource; + ConfigWidget *mConfigWidget; + bool mDifferentComic; + bool mShowComicUrl; + bool mShowComicAuthor; + bool mShowComicTitle; + bool mShowComicIdentifier; + bool mShowErrorPicture; + bool mArrowsOnHover; + bool mMiddleClick; + int mCheckNewComicStripsInterval; + int mMaxComicLimit; + CheckNewStrips *mCheckNewStrips; + QTimer *mDateChangedTimer; + QList mActions; + QAction *mActionGoFirst; + QAction *mActionGoLast; + QAction *mActionGoJump; + QAction *mActionScaleContent; + QAction *mActionWebsite; + QAction *mActionShop; + QAction *mActionStorePosition; + QAction *mActionNextNewStripTab; + QAction *mActionSaveComicAs; + QAction *mActionCreateComicBook; + QSizeF mMaxSize; + QSizeF mLastSize; + QSizeF mIdealSize; + ComicEngine *const mEngine; + + // Tabs + bool mTabAdded; + QStringList mTabIdentifier; + + ComicData mCurrent; + SavingDir *mSavingDir; +}; + +#endif diff --git a/applets/comic/comic.knsrc b/applets/comic/comic.knsrc new file mode 100644 index 00000000..25270486 --- /dev/null +++ b/applets/comic/comic.knsrc @@ -0,0 +1,61 @@ +[KNewStuff3] +Name=Comics +Name[ar]=كاريكاتير +Name[ast]=Cómics +Name[az]=Komikslər +Name[bg]=Комикси +Name[ca]=Còmics +Name[ca@valencia]=Còmics +Name[cs]=Komiksy +Name[da]=Tegneserier +Name[de]=Comics +Name[el]=Κόμικς +Name[en_GB]=Comics +Name[eo]=Bildstrio +Name[es]=Cómics +Name[et]=Koomiks +Name[eu]=Komikiak +Name[fi]=Sarjakuvat +Name[fr]=Bandes dessinées +Name[gl]=Bandas deseñadas +Name[he]=קומיקסים +Name[hu]=Képregény +Name[ia]=Comics +Name[id]=Komik +Name[is]=Myndasögur +Name[it]=Fumetti +Name[ja]=漫画 +Name[ka]=კომიქსები +Name[ko]=만화 +Name[lt]=Komiksai +Name[lv]=Komikss +Name[nb]=Tegneserier +Name[nl]=Stripboeken +Name[nn]=Teikneseriar +Name[pa]=ਕਾਮਿਕ +Name[pl]=Komiksy +Name[pt]=Banda Desenhada +Name[pt_BR]=Tirinhas +Name[ro]=Benzi desenate +Name[ru]=Комиксы +Name[sk]=Komiks +Name[sl]=Stripi +Name[sr]=стрипови +Name[sr@ijekavian]=стрипови +Name[sr@ijekavianlatin]=stripovi +Name[sr@latin]=stripovi +Name[sv]=Serier +Name[tg]=Лаҳзаи гуворо +Name[tr]=Çizgi Romanlar +Name[uk]=Комікси +Name[vi]=Truyện tranh +Name[x-test]=xxComicsxx +Name[zh_CN]=连环画 +Name[zh_TW]=漫畫 + +Categories=Plasma Comic +ContentWarning=Executables +TargetDir=plasma/comics +Uncompress=kpackage +KPackageStructure=Plasma/Comic + diff --git a/applets/comic/comicdata.cpp b/applets/comic/comicdata.cpp new file mode 100644 index 00000000..23627044 --- /dev/null +++ b/applets/comic/comicdata.cpp @@ -0,0 +1,104 @@ +/* + * SPDX-FileCopyrightText: 2012 Matthias Fuchs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "comicdata.h" + +#include +#include +// Qt +#include + +ComicData::ComicData() +{ +} + +void ComicData::init(const QString &id, const KConfigGroup &config) +{ + mId = id; + mCfg = config; + load(); +} + +void ComicData::load() +{ + mScaleComic = mCfg.readEntry(QLatin1String("scaleToContent_") + mId, false); + mMaxStripNum = mCfg.readEntry(QLatin1String("maxStripNum_") + mId, 0); + mStored = mCfg.readEntry(QLatin1String("storedPosition_") + mId, QString()); +} + +void ComicData::save() +{ + mCfg.writeEntry(QLatin1String("scaleToContent_") + mId, mScaleComic); + mCfg.writeEntry(QLatin1String("maxStripNum_") + mId, mMaxStripNum); + mCfg.writeEntry(QLatin1String("storedPosition_") + id(), mStored); + + // no next, thus the most recent strip + if (!hasNext()) { + mCfg.writeEntry(QLatin1String("lastStripVisited_") + mId, true); + mCfg.writeEntry(QLatin1String("lastStrip_") + mId, mLast); + } +} + +void ComicData::setScaleComic(bool scale) +{ + mScaleComic = scale; + save(); +} + +void ComicData::storePosition(bool store) +{ + mStored = (store ? mCurrent : QString()); + save(); +} + +void ComicData::setData(const ComicMetaData &data) +{ + if (!data.error) { + mImage = data.image; + mPrev = data.previousIdentifier; + mNext = data.nextIdentifier; + mAdditionalText = data.additionalText; + mReady = true; + } + + mWebsiteUrl = data.websiteUrl; + mImageUrl = data.imageUrl; + mShopUrl = data.shopUrl; + mFirst = data.firstStripIdentifier; + mStripTitle = data.stripTitle; + mAuthor = data.comicAuthor; + mTitle = data.providerName; + mType = data.identifierType; + + QString temp = data.identifier; + mCurrent = temp.remove(mId + QLatin1Char(':')); + + // found a new last identifier + if (!hasNext()) { + mLast = mCurrent; + } + + mCurrentReadable.clear(); + if (mType == IdentifierType::NumberIdentifier) { + mCurrentReadable = i18nc("an abbreviation for Number", "# %1", mCurrent); + int tempNum = mCurrent.toInt(); + if (mMaxStripNum < tempNum) { + mMaxStripNum = tempNum; + } + + temp = mFirst.remove(mId + QLatin1Char(':')); + mFirstStripNum = temp.toInt(); + } else if (mType == IdentifierType::DateIdentifier && QDate::fromString(temp, QStringLiteral("yyyy-MM-dd")).isValid()) { + mCurrentReadable = mCurrent; + } else if (mType == IdentifierType::StringIdentifier) { + mCurrentReadable = mCurrent; + } + + mIsLeftToRight = data.isLeftToRight; + mIsTopToBottom = data.isTopToBottom; + + save(); +} diff --git a/applets/comic/comicdata.h b/applets/comic/comicdata.h new file mode 100644 index 00000000..456d849b --- /dev/null +++ b/applets/comic/comicdata.h @@ -0,0 +1,239 @@ +/* + * SPDX-FileCopyrightText: 2012 Matthias Fuchs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef COMIC_DATA_H +#define COMIC_DATA_H + +#include "comicinfo.h" +#include "engine/types.h" + +// Qt +#include +#include +#include +#include + +class ComicData +{ +public: + ComicData(); + + void init(const QString &id, const KConfigGroup &config); + + void setData(const ComicMetaData &data); + + IdentifierType type() const + { + return mType; + } + + /** + * The identifier of the comic, e.g. "garfield" + */ + QString id() const + { + return mId; + } + + /** + * The stored comic e.g. "2007-12-21" for a comic of the Date type + */ + QString stored() const + { + return mStored; + } + + void storePosition(bool store); + + /** + * The previous comic e.g. "2007-12-21" for a comic of the Date type + */ + QString prev() const + { + return mPrev; + } + + /** + * The current comic e.g. "2007-12-21" for a comic of the Date type + */ + QString current() const + { + return mCurrent; + } + + /** + * The next comic e.g. "2007-12-21" for a comic of the Date type + */ + QString next() const + { + return mNext; + } + + QString currentReadable() const + { + return mCurrentReadable; + } + + /** + * The first comic e.g. "2007-12-21" for a comic of the Date type + */ + QString first() const + { + return mFirst; + } + + bool hasNext() const + { + return !mNext.isEmpty(); + } + + bool hasPrev() const + { + return !mPrev.isEmpty(); + } + + bool hasFirst() const + { + return !mFirst.isEmpty(); + } + + bool hasStored() const + { + return !mStored.isEmpty(); + } + + bool hasImage() const + { + return !mImage.isNull(); + } + + QString additionalText() const + { + return mAdditionalText; + } + + QString title() const + { + return mTitle; + } + void setTitle(const QString &title) + { + mTitle = title; + } + + QString stripTitle() const + { + return mStripTitle; + } + + QUrl websiteUrl() const + { + return mWebsiteUrl; + } + + QUrl imageUrl() const + { + return mImageUrl; + } + + QUrl shopUrl() const + { + return mShopUrl; + } + + QString author() const + { + return mAuthor; + } + + QImage image() const + { + return mImage; + } + + bool scaleComic() const + { + return mScaleComic; + } + + bool isLeftToRight() const + { + return mIsLeftToRight; + } + + bool isTopToBottom() const + { + return mIsTopToBottom; + } + + bool storePosition() const + { + return !mStored.isEmpty(); + } + + void setScaleComic(bool scale); + + QString errorStrip() const + { + return mErrorStrip; + } + + int firstStripNum() const + { + return mFirstStripNum; + } + + int maxStripNum() const + { + return mMaxStripNum; + } + + bool ready() const + { + return mReady; + } + + void save(); + +private: + void load(); + +private: + IdentifierType mType; + QString mId; + QString mFirst; + QString mLast; + QString mCurrent; + QString mNext; + QString mPrev; + QString mStored; + QString mCurrentReadable; + + QString mErrorStrip; + + QString mAuthor; + QString mTitle; + QString mStripTitle; + QString mAdditionalText; + QUrl mWebsiteUrl; + QUrl mImageUrl; + QUrl mShopUrl; + + QImage mImage; + + // only applicable if the comic is of type Number + int mFirstStripNum = 0; + int mMaxStripNum = 0; + + bool mScaleComic = false; + bool mIsLeftToRight = false; + bool mIsTopToBottom = false; + bool mReady = false; + + KConfigGroup mCfg; +}; + +#endif diff --git a/applets/comic/comicinfo.cpp b/applets/comic/comicinfo.cpp new file mode 100644 index 00000000..90164e64 --- /dev/null +++ b/applets/comic/comicinfo.cpp @@ -0,0 +1,100 @@ +/* + * SPDX-FileCopyrightText: 2012 Matthias Fuchs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "comicinfo.h" + +#include +// Qt +#include +#include + +class SavingDir::SavingDirPrivate +{ +public: + SavingDirPrivate(const KConfigGroup &cfg); + + void init(); + + QString getDir() const; + void setDir(const QString &dir); + +private: + void load(); + void save(); + bool isValid(); + +private: + KConfigGroup mCfg; + QString mDir; +}; + +SavingDir::SavingDirPrivate::SavingDirPrivate(const KConfigGroup &cfg) + : mCfg(cfg) +{ +} + +void SavingDir::SavingDirPrivate::init() +{ + load(); + save(); +} + +QString SavingDir::SavingDirPrivate::getDir() const +{ + return mDir; +} + +void SavingDir::SavingDirPrivate::setDir(const QString &dir) +{ + mDir = dir; + save(); +} + +void SavingDir::SavingDirPrivate::load() +{ + mDir = mCfg.readEntry("savingDir", QString()); + if (!isValid()) { + mDir = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); + } + if (!isValid()) { + mDir = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); + } + if (!isValid()) { + mDir = QDir::homePath(); + } +} + +void SavingDir::SavingDirPrivate::save() +{ + mCfg.writeEntry("savingDir", mDir); +} + +bool SavingDir::SavingDirPrivate::isValid() +{ + QDir dir; + return (!mDir.isEmpty() && dir.exists(mDir)); +} + +SavingDir::SavingDir(const KConfigGroup &cfg) + : d(new SavingDirPrivate(cfg)) +{ + d->init(); +} + +SavingDir::~SavingDir() +{ + delete d; +} + +QString SavingDir::getDir() const +{ + return d->getDir(); +} + +void SavingDir::setDir(const QString &dir) +{ + d->setDir(dir); +} diff --git a/applets/comic/comicinfo.h b/applets/comic/comicinfo.h new file mode 100644 index 00000000..b57c855b --- /dev/null +++ b/applets/comic/comicinfo.h @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: 2011-2012 Matthias Fuchs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef COMICINFO_H +#define COMICINFO_H + +class KConfigGroup; +class QString; + +/** + * Provides access (read/write) to the directory that should be used + * whenever the user is presented with a file selection dialog. + */ +class SavingDir +{ +public: + /** + * @param config the config that should be used to retrieve + * the saving directory and to store it to in case of changes + */ + explicit SavingDir(const KConfigGroup &config); + + ~SavingDir(); + + /** + * @return the directory to be displayed to the user + */ + QString getDir() const; + + /** + * Set the directory that should be displayed to the user first + * when choosing a destination. Automatically writes the directory + * to the config, if one was specified in init. + * @param dir the directory to display the user first + * @see init + */ + void setDir(const QString &dir); + +private: + class SavingDirPrivate; + SavingDirPrivate *d; +}; + +#endif diff --git a/applets/comic/comicmodel.cpp b/applets/comic/comicmodel.cpp new file mode 100644 index 00000000..6fec5138 --- /dev/null +++ b/applets/comic/comicmodel.cpp @@ -0,0 +1,83 @@ +/* + * SPDX-FileCopyrightText: 2007 Tobias Koenig + * SPDX-FileCopyrightText: 2008-2010 Matthias Fuchs + * SPDX-FileCopyrightText: 2015 Marco Martin + * SPDX-FileCopyrightText: 2022 Alexander Lohnau + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "comicmodel.h" +#include "engine/comicprovider.h" + +#include +#include + +ComicModel::ComicModel(ComicEngine *engine, const QStringList &usedComics, QObject *parent) + : QAbstractTableModel(parent) + , mUsedComics(usedComics) + , mEngine(engine) +{ + Q_ASSERT(engine); + + load(); +} + +QHash ComicModel::roleNames() const +{ + QHash roles; + roles[Qt::DisplayRole] = "display"; + roles[Qt::DecorationRole] = "decoration"; + roles[Qt::UserRole] = "plugin"; + return roles; +} + +int ComicModel::rowCount(const QModelIndex &index) const +{ + if (!index.isValid()) { + return mComics.count(); + } + + return 0; +} + +int ComicModel::columnCount(const QModelIndex &index) const +{ + Q_UNUSED(index) + return 2; +} + +QVariant ComicModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= mComics.count()) { + return QVariant(); + } + + const ComicProviderInfo &info = mComics.at(index.row()); + switch (role) { + case Qt::DisplayRole: + return info.name; + case Qt::DecorationRole: + return QIcon::fromTheme(info.icon); + case Qt::UserRole: + return info.pluginId; + } + + return QVariant(); +} + +Qt::ItemFlags ComicModel::flags(const QModelIndex &index) const +{ + if (index.isValid() && (index.column() == 0)) { + return QAbstractItemModel::flags(index) | Qt::ItemIsUserCheckable; + } + + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; +} + +void ComicModel::load() +{ + beginResetModel(); + mComics = mEngine->loadProviders(); + endResetModel(); +} diff --git a/applets/comic/comicmodel.h b/applets/comic/comicmodel.h new file mode 100644 index 00000000..cf6ae937 --- /dev/null +++ b/applets/comic/comicmodel.h @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: 2007 Tobias Koenig + * SPDX-FileCopyrightText: 2008-2010 Matthias Fuchs + * SPDX-FileCopyrightText: 2015 Marco Martin + * SPDX-FileCopyrightText: 2022 Alexander Lohnau + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef COMICMODEL_H +#define COMICMODEL_H + +#include + +#include "engine/comic.h" + +class ComicModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + ComicModel(ComicEngine *engine, const QStringList &usedComics, QObject *parent = nullptr); + + QHash roleNames() const override; + + int rowCount(const QModelIndex &index = QModelIndex()) const override; + int columnCount(const QModelIndex &index = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::CheckStateRole) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + + void load(); + +private: + QList mComics; + QStringList mUsedComics; + ComicEngine *mEngine; +}; + +#endif diff --git a/applets/comic/comicsaver.cpp b/applets/comic/comicsaver.cpp new file mode 100644 index 00000000..255ef3b7 --- /dev/null +++ b/applets/comic/comicsaver.cpp @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: 2008-2012 Matthias Fuchs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "comicsaver.h" +#include "comicdata.h" +#include "comicinfo.h" + +#include +#include +#include + +#include + +ComicSaver::ComicSaver(SavingDir *savingDir) + : mSavingDir(savingDir) +{ +} + +bool ComicSaver::save(const ComicData &comic) +{ + const QString title = comic.title(); + + const QString name = title + QLatin1String(" - ") + comic.current() + QLatin1String(".png"); + QUrl destUrl = QUrl::fromLocalFile(mSavingDir->getDir() + QLatin1Char('/') + name); + + destUrl = QFileDialog::getSaveFileUrl(nullptr, QString(), destUrl, QStringLiteral("*.png")); + + if (!destUrl.isValid()) { + return false; + } + + mSavingDir->setDir(destUrl.path()); + comic.image().save(destUrl.toLocalFile(), "PNG"); + + return true; +} diff --git a/applets/comic/comicsaver.h b/applets/comic/comicsaver.h new file mode 100644 index 00000000..b065d9a4 --- /dev/null +++ b/applets/comic/comicsaver.h @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: 2008-2012 Matthias Fuchs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef COMIC_SAVER_H +#define COMIC_SAVER_H + +class ComicData; +class SavingDir; + +/** + * ComicSaver takes care of saving a comic strip to a user chosen + * destination. + * etc. + */ +class ComicSaver +{ +public: + explicit ComicSaver(SavingDir *savingDir); + + /** + * Asks the user for a destination to save the specified + * comic to. If possible writes it to that destination. + * @param comic the comic to save + * @return true if saving worked, false if there was a problem + */ + bool save(const ComicData &comic); + +private: + SavingDir *mSavingDir; +}; + +#endif diff --git a/applets/comic/engine/CMakeLists.txt b/applets/comic/engine/CMakeLists.txt new file mode 100644 index 00000000..f53f2346 --- /dev/null +++ b/applets/comic/engine/CMakeLists.txt @@ -0,0 +1,31 @@ +# SPDX-License-Identifier: BSD-3-Clause +# SPDX-FileCopyrightText: 2022 Alexander Lohnau + +add_library(plasma_engine_comic STATIC) + +set_property(TARGET plasma_engine_comic PROPERTY POSITION_INDEPENDENT_CODE ON) + +ecm_qt_declare_logging_category(plasma_engine_comic + HEADER comic_debug.h + IDENTIFIER PLASMA_COMIC + CATEGORY_NAME ork.kde.plasma.comic + DESCRIPTION "Plasma Comic Engine" + EXPORT KDEPLASMAADDONS +) + +target_sources(plasma_engine_comic PRIVATE + comicprovider.cpp + cachedprovider.cpp + comic.cpp + comicproviderkross.cpp + comicproviderwrapper.cpp +) +target_link_libraries(plasma_engine_comic + Qt::Qml + KF6::WidgetsAddons + KF6::Package + KF6::KIOCore +) + +kcoreaddons_add_plugin(plasma_comic SOURCES comic_package.cpp INSTALL_NAMESPACE "kf6/packagestructure") +target_link_libraries(plasma_comic KF6::ConfigCore KF6::Package) diff --git a/applets/comic/engine/cachedprovider.cpp b/applets/comic/engine/cachedprovider.cpp new file mode 100644 index 00000000..83a5f265 --- /dev/null +++ b/applets/comic/engine/cachedprovider.cpp @@ -0,0 +1,231 @@ +/* + * SPDX-FileCopyrightText: 2007 Tobias Koenig + * SPDX-FileCopyrightText: 2022 Alexander Lohnau + * + * SPDX-License-Identifier: LGPL-2.0-only + */ + +#include "cachedprovider.h" +#include "comic_debug.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +const int CachedProvider::CACHE_DEFAULT = 20; + +static QString identifierToPath(const QString &identifier) +{ + const QString dataDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/plasma_engine_comic/"); + + return dataDir + QString::fromLatin1(QUrl::toPercentEncoding(identifier)); +} + +CachedProvider::CachedProvider(QObject *parent, const KPluginMetaData &data, IdentifierType type, const QString &identifier) + : ComicProvider(parent, data, type, identifier) +{ + QTimer::singleShot(0, this, &CachedProvider::triggerFinished); +} + +CachedProvider::~CachedProvider() +{ +} + +IdentifierType CachedProvider::identifierType() const +{ + return IdentifierType::StringIdentifier; +} + +QImage CachedProvider::image() const +{ + if (!QFile::exists(identifierToPath(requestedString()))) { + return QImage(); + } + + QImage img; + img.load(identifierToPath(requestedString()), "PNG"); + + return img; +} + +QString CachedProvider::identifier() const +{ + if (requestedString().lastIndexOf(QLatin1Char(':')) + 1 == requestedString().size()) { + return requestedString() + lastCachedStripIdentifier(); + } + return requestedString(); +} + +QString CachedProvider::nextIdentifier() const +{ + QSettings settings(identifierToPath(requestedString()) + QLatin1String(".conf"), QSettings::IniFormat); + return settings.value(QLatin1String("nextIdentifier"), QString()).toString(); +} + +QString CachedProvider::previousIdentifier() const +{ + QSettings settings(identifierToPath(requestedString()) + QLatin1String(".conf"), QSettings::IniFormat); + return settings.value(QLatin1String("previousIdentifier"), QString()).toString(); +} + +QString CachedProvider::firstStripIdentifier() const +{ + QSettings settings(identifierToPath(requestedComicName()) + QLatin1String(".conf"), QSettings::IniFormat); + return settings.value(QLatin1String("firstStripIdentifier"), QString()).toString(); +} + +QString CachedProvider::lastCachedStripIdentifier() const +{ + QSettings settings(identifierToPath(requestedComicName()) + QLatin1String(".conf"), QSettings::IniFormat); + return settings.value(QLatin1String("lastCachedStripIdentifier"), QString()).toString(); +} + +QString CachedProvider::comicAuthor() const +{ + QSettings settings(identifierToPath(requestedString()) + QLatin1String(".conf"), QSettings::IniFormat); + return settings.value(QLatin1String("comicAuthor"), QString()).toString(); +} + +QString CachedProvider::stripTitle() const +{ + QSettings settings(identifierToPath(requestedString()) + QLatin1String(".conf"), QSettings::IniFormat); + return settings.value(QLatin1String("stripTitle"), QString()).toString(); +} + +QString CachedProvider::additionalText() const +{ + QSettings settings(identifierToPath(requestedString()) + QLatin1String(".conf"), QSettings::IniFormat); + return settings.value(QLatin1String("additionalText"), QString()).toString(); +} + +QString CachedProvider::name() const +{ + QSettings settings(identifierToPath(requestedComicName()) + QLatin1String(".conf"), QSettings::IniFormat); + return settings.value(QLatin1String("title"), QString()).toString(); +} + +void CachedProvider::triggerFinished() +{ + Q_EMIT finished(this); +} + +bool CachedProvider::isCached(const QString &identifier) +{ + return QFile::exists(identifierToPath(identifier)); +} + +bool CachedProvider::storeInCache(const QString &identifier, const QImage &comic, const ComicMetaData &data) +{ + const QString path = identifierToPath(identifier); + + int index = identifier.indexOf(QLatin1Char(':')); + const QString comicName = identifier.mid(0, index); + const QString pathMain = identifierToPath(comicName); + const QString dirPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/plasma_engine_comic/"); + + QSettings settingsMain(pathMain + QLatin1String(".conf"), QSettings::IniFormat); + settingsMain.setValue(QStringLiteral("firstStripIdentifier"), data.firstStripIdentifier); + settingsMain.setValue(QStringLiteral("title"), data.providerName); + settingsMain.setValue(QStringLiteral("lastCachedStripIdentifier"), data.lastCachedStripIdentifier); + settingsMain.setValue(QStringLiteral("shopUrl"), data.shopUrl); + settingsMain.setValue(QStringLiteral("isLeftToRight"), data.isLeftToRight); + settingsMain.setValue(QStringLiteral("isTopToBottom"), data.isTopToBottom); + + QSettings settings(path + QLatin1String(".conf"), QSettings::IniFormat); + settings.setValue(QStringLiteral("additionalText"), data.additionalText); + settings.setValue(QStringLiteral("comicAuthor"), data.comicAuthor); + settings.setValue(QStringLiteral("imageUrl"), data.imageUrl); + settings.setValue(QStringLiteral("nextIdentifier"), data.nextIdentifier); + settings.setValue(QStringLiteral("previousIdentifier"), data.previousIdentifier); + settings.setValue(QStringLiteral("websiteUrl"), data.websiteUrl); + + QStringList comics; + if (settingsMain.contains(QLatin1String("comics"))) { + comics = settingsMain.value(QLatin1String("comics"), QStringList()).toStringList(); + } else { + // existing strips haven't been stored in the conf-file yet, do that now, oldest first, newest last + QDir dir(dirPath); + comics = dir.entryList(QStringList() << QString::fromLatin1(QUrl::toPercentEncoding(comicName + QLatin1Char(':'))) + QLatin1Char('*'), + QDir::Files, + QDir::Time | QDir::Reversed); + QStringList::iterator it = comics.begin(); + while (it != comics.end()) { + // only count images, not the conf files + if ((*it).endsWith(QLatin1String(".conf"))) { + it = comics.erase(it); + } else { + ++it; + } + } + } + comics.append(QString::fromLatin1(QUrl::toPercentEncoding(identifier))); + + const int limit = CachedProvider::maxComicLimit(); + // limit is on + if (limit > 0) { + qCDebug(PLASMA_COMIC) << QLatin1String("MaxComicLimit on."); + int comicsToRemove = comics.count() - limit; + QStringList::iterator it = comics.begin(); + while (comicsToRemove > 0 && it != comics.end()) { + qCDebug(PLASMA_COMIC) << QLatin1String("Remove file") << (dirPath + (*it)); + QFile::remove(dirPath + (*it)); + QFile::remove(dirPath + (*it) + QLatin1String(".conf")); + it = comics.erase(it); + --comicsToRemove; + } + } + settingsMain.setValue(QLatin1String("comics"), comics); + + return comic.save(path, "PNG"); +} + +QUrl CachedProvider::websiteUrl() const +{ + QSettings settings(identifierToPath(requestedString()) + QLatin1String(".conf"), QSettings::IniFormat); + return QUrl(settings.value(QLatin1String("websiteUrl")).toUrl()); +} + +QUrl CachedProvider::imageUrl() const +{ + QSettings settings(identifierToPath(requestedString()) + QLatin1String(".conf"), QSettings::IniFormat); + return QUrl(settings.value(QLatin1String("imageUrl")).toUrl()); +} + +QUrl CachedProvider::shopUrl() const +{ + QSettings settings(identifierToPath(requestedComicName()) + QLatin1String(".conf"), QSettings::IniFormat); + return QUrl(settings.value(QLatin1String("shopUrl")).toUrl()); +} + +bool CachedProvider::isLeftToRight() const +{ + QSettings settings(identifierToPath(requestedComicName()) + QLatin1String(".conf"), QSettings::IniFormat); + return settings.value(QLatin1String("isLeftToRight"), true).toBool(); +} + +bool CachedProvider::isTopToBottom() const +{ + QSettings settings(identifierToPath(requestedComicName()) + QLatin1String(".conf"), QSettings::IniFormat); + return settings.value(QLatin1String("isTopToBottom"), true).toBool(); +} + +int CachedProvider::maxComicLimit() +{ + QSettings settings(identifierToPath(QLatin1String("comic_settings.conf")), QSettings::IniFormat); + return qMax(settings.value(QLatin1String("maxComics"), CACHE_DEFAULT).toInt(), 0); // old value was -1, thus use qMax +} + +void CachedProvider::setMaxComicLimit(int limit) +{ + if (limit < 0) { + qCDebug(PLASMA_COMIC) << "Wrong limit, setting to default."; + limit = CACHE_DEFAULT; + } + QSettings settings(identifierToPath(QLatin1String("comic_settings.conf")), QSettings::IniFormat); + settings.setValue(QLatin1String("maxComics"), limit); +} diff --git a/applets/comic/engine/cachedprovider.h b/applets/comic/engine/cachedprovider.h new file mode 100644 index 00000000..504c411a --- /dev/null +++ b/applets/comic/engine/cachedprovider.h @@ -0,0 +1,152 @@ +/* + * SPDX-FileCopyrightText: 2007 Tobias Koenig + * SPDX-FileCopyrightText: 2022 Alexander Lohnau + * + * SPDX-License-Identifier: LGPL-2.0-only + */ + +#ifndef CACHEDPROVIDER_H +#define CACHEDPROVIDER_H + +#include "comicprovider.h" +#include "types.h" + +#include + +/** + * This class provides comics from the local cache. + */ +class CachedProvider : public ComicProvider +{ + Q_OBJECT + +public: + /** + * Creates a new cached provider. + * + * @param parent The parent object. + * param args The arguments. + */ + explicit CachedProvider(QObject *parent, const KPluginMetaData &data, IdentifierType type, const QString &identifier); + + /** + * Destroys the cached provider. + */ + ~CachedProvider() override; + + /** + * Returns the identifier type. + * + * Is always StringIdentifier here. + */ + IdentifierType identifierType() const override; + + /** + * Returns the requested image. + * + * Note: This method returns only a valid image after the + * finished() signal has been emitted. + */ + QImage image() const override; + + /** + * Returns the identifier of the comic request (name + date). + */ + QString identifier() const override; + + /** + * Returns the identifier suffix of the next comic. + */ + QString nextIdentifier() const override; + + /** + * Returns the identifier suffix of the previous comic. + */ + QString previousIdentifier() const override; + + /** + * Returns the identifier of the first strip. + */ + QString firstStripIdentifier() const override; + + /** + * Returns the identifier of the last cached strip. + */ + QString lastCachedStripIdentifier() const; + + /** + * Returns the title of the strip. + */ + QString stripTitle() const override; + + /** + * Returns the author of the comic. + */ + QString comicAuthor() const override; + + /** + * Returns additionalText of the comic. + */ + QString additionalText() const override; + + /** + * Returns the name for the comic + */ + QString name() const override; + + /** + * Returns whether the comic is leftToRight or not + */ + bool isLeftToRight() const override; + + /** + * Returns whether the comic is topToBottom or not + */ + bool isTopToBottom() const override; + + /** + * Returns whether a comic with the given @p identifier is cached. + */ + static bool isCached(const QString &identifier); + + /** + * Map of keys and values to store in the config file for an individual identifier + */ + typedef QHash Settings; + + /** + * Stores the given @p comic with the given @p identifier in the cache. + */ + static bool storeInCache(const QString &identifier, const QImage &comic, const ComicMetaData &info); + + /** + * Returns the website of the comic. + */ + QUrl websiteUrl() const override; + + QUrl imageUrl() const override; + + /** + * Returns the shop website of the comic. + */ + QUrl shopUrl() const override; + + /** + * Returns the maximum number of cached strips per comic, -1 means that there is no limit + * @note defaulte is -1 + */ + static int maxComicLimit(); + + /** + * Sets the maximum number of cached strips per comic, -1 means that there is no limit + */ + static void setMaxComicLimit(int limit); + +private Q_SLOTS: + void triggerFinished(); + +private: + static const int CACHE_DEFAULT; +}; + +#endif diff --git a/applets/comic/engine/comic.cpp b/applets/comic/engine/comic.cpp new file mode 100644 index 00000000..390aef04 --- /dev/null +++ b/applets/comic/engine/comic.cpp @@ -0,0 +1,248 @@ +/* + * SPDX-FileCopyrightText: 2007 Tobias Koenig + * SPDX-FileCopyrightText: 2022 Alexander Lohnau + * + * SPDX-License-Identifier: LGPL-2.0-only + */ + +#include "comic.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "cachedprovider.h" +#include "comic_debug.h" +#include "comicprovider.h" +#include "comicproviderkross.h" + +ComicEngine::ComicEngine(QObject *parent) + : QObject(parent) + , mEmptySuffix(false) +{ + QNetworkInformation::instance()->loadBackendByFeatures(QNetworkInformation::Feature::Reachability); + loadProviders(); +} + +QList ComicEngine::loadProviders() +{ + mProviders.clear(); + const auto comics = KPackage::PackageLoader::self()->listKPackages(QStringLiteral("Plasma/Comic")); + QList providers; + for (auto comicPackage : comics) { + const KPluginMetaData comic = comicPackage.metadata(); + qCDebug(PLASMA_COMIC) << "ComicEngine::loadProviders() service name=" << comic.name(); + ComicProviderInfo data; + data.pluginId = comic.pluginId(); + data.name = comic.name(); + QFileInfo file(comic.iconName()); + if (file.isRelative()) { + data.icon = + QStandardPaths::locate(QStandardPaths::GenericDataLocation, QString::fromLatin1("plasma/comics/%1/%2").arg(comic.pluginId(), comic.iconName())); + } else { + data.icon = comic.iconName(); + } + mProviders << comic.pluginId(); + providers << data; + } + return providers; +} + +void ComicEngine::setMaxComicLimit(int maxComicLimit) +{ + CachedProvider::setMaxComicLimit(maxComicLimit); +} + +bool ComicEngine::requestSource(const QString &identifier) +{ + if (m_jobs.contains(identifier)) { + return true; + } + + const QStringList parts = identifier.split(QLatin1Char(':'), Qt::KeepEmptyParts); + + // check whether it is cached, make sure second part present + if (parts.count() > 1 && (CachedProvider::isCached(identifier) || !isOnline())) { + ComicProvider *provider = new CachedProvider(this, KPluginMetaData{}, IdentifierType::StringIdentifier, identifier); + m_jobs[identifier] = provider; + connect(provider, &ComicProvider::finished, this, &ComicEngine::finished); + connect(provider, &ComicProvider::error, this, &ComicEngine::error); + return true; + } + + // ... start a new query otherwise + if (parts.count() < 2) { + Q_EMIT requestFinished(ComicMetaData{.error = true}); + qWarning() << "Less than two arguments specified."; + return false; + } + if (!mProviders.contains(parts[0])) { + // User might have installed more from GHNS + loadProviders(); + if (!mProviders.contains(parts[0])) { + Q_EMIT requestFinished(ComicMetaData{.error = true}); + qWarning() << identifier << "comic plugin does not seem to be installed."; + return false; + } + } + + // check if there is a connection + if (!isOnline()) { + qCDebug(PLASMA_COMIC) << "Currently offline, requested identifier was" << mIdentifierError; + mIdentifierError = identifier; + ComicMetaData data; + data.error = true; + data.errorAutomaticallyFixable = true; + data.identifier = identifier; + data.previousIdentifier = lastCachedIdentifier(identifier); + Q_EMIT requestFinished(data); + qCDebug(PLASMA_COMIC) << "No internet connection, using cached data"; + return true; + } + + KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Comic"), parts[0]); + + bool isCurrentComic = parts[1].isEmpty(); + + ComicProvider *provider = nullptr; + + QVariant data; + const IdentifierType identifierType = stringToIdentifierType(pkg.metadata().value(QStringLiteral("X-KDE-PlasmaComicProvider-SuffixType"))); + if (identifierType == IdentifierType::DateIdentifier) { + QDate date = QDate::fromString(parts[1], Qt::ISODate); + if (!date.isValid()) { + date = QDate::currentDate(); + } + + data = date; + } else if (identifierType == IdentifierType::NumberIdentifier) { + data = parts[1].toInt(); + } else if (identifierType == IdentifierType::StringIdentifier) { + data = parts[1]; + } + provider = new ComicProviderKross(this, pkg.metadata(), identifierType, data); + provider->setIsCurrent(isCurrentComic); + + m_jobs[identifier] = provider; + + connect(provider, &ComicProvider::finished, this, &ComicEngine::finished); + connect(provider, &ComicProvider::error, this, &ComicEngine::error); + return true; +} + +void ComicEngine::finished(ComicProvider *provider) +{ + // sets the data + if (provider->image().isNull()) { + qCWarning(PLASMA_COMIC) << "Provider returned null image" << provider->name(); + error(provider); + return; + } + + ComicMetaData data = metaDataFromProvider(provider); + + // different comic -- with no error yet -- has been chosen, old error is invalidated + QString temp = mIdentifierError.left(mIdentifierError.indexOf(QLatin1Char(':')) + 1); + if (!mIdentifierError.isEmpty() && provider->identifier().indexOf(temp) == -1) { + mIdentifierError.clear(); + } + // comic strip with error worked now + if (!mIdentifierError.isEmpty() && (mIdentifierError == provider->identifier())) { + mIdentifierError.clear(); + } + + // store in cache if it's not the response of a CachedProvider, + if (!provider->inherits("CachedProvider") && !provider->image().isNull()) { + CachedProvider::storeInCache(provider->identifier(), provider->image(), data); + } + provider->deleteLater(); + + const QString key = m_jobs.key(provider); + if (!key.isEmpty()) { + m_jobs.remove(key); + } + Q_EMIT requestFinished(data); +} + +void ComicEngine::error(ComicProvider *provider) +{ + QString identifier(provider->identifier()); + mIdentifierError = identifier; + + qWarning() << identifier << "plugging reported an error."; + + ComicMetaData data = metaDataFromProvider(provider); + data.error = true; + + // if there was an error loading the last cached comic strip, do not return its id anymore + const QString lastCachedId = lastCachedIdentifier(identifier); + if (lastCachedId != provider->identifier().mid(provider->identifier().indexOf(QLatin1Char(':')) + 1)) { + // sets the previousIdentifier to the identifier of a strip that has been cached before + data.previousIdentifier = lastCachedId; + } + data.nextIdentifier = QString(); + + const QString key = m_jobs.key(provider); + if (!key.isEmpty()) { + m_jobs.remove(key); + } + + provider->deleteLater(); + Q_EMIT requestFinished(data); +} + +ComicMetaData ComicEngine::metaDataFromProvider(ComicProvider *provider) +{ + QString identifier(provider->identifier()); + + /** + * Requests for the current day have no suffix (date or id) + * set initially, so we have to remove the 'faked' suffix + * here again to not confuse the applet. + */ + if (provider->isCurrent()) { + identifier = identifier.left(identifier.indexOf(QLatin1Char(':')) + 1); + } + + ComicMetaData data; + data.imageUrl = provider->imageUrl(); + data.image = provider->image(); + data.websiteUrl = provider->websiteUrl(); + data.shopUrl = provider->shopUrl(); + data.nextIdentifier = provider->nextIdentifier(); + data.previousIdentifier = provider->previousIdentifier(); + data.comicAuthor = provider->comicAuthor(); + data.additionalText = provider->additionalText(); + data.stripTitle = provider->stripTitle(); + data.firstStripIdentifier = provider->firstStripIdentifier(); + data.identifier = identifier; + data.providerName = provider->name(); + data.identifierType = provider->identifierType(); + data.isLeftToRight = provider->isLeftToRight(); + data.isTopToBottom = provider->isTopToBottom(); + + return data; +} + +QString ComicEngine::lastCachedIdentifier(const QString &identifier) const +{ + const QString id = identifier.left(identifier.indexOf(QLatin1Char(':'))); + QString data = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/plasma_engine_comic/"); + data += QString::fromLatin1(QUrl::toPercentEncoding(id)); + QSettings settings(data + QLatin1String(".conf"), QSettings::IniFormat); + QString previousIdentifier = settings.value(QLatin1String("lastCachedStripIdentifier"), QString()).toString(); + + return previousIdentifier; +} + +bool ComicEngine::isOnline() const +{ + return QNetworkInformation::instance()->reachability() == QNetworkInformation::Reachability::Online; +} diff --git a/applets/comic/engine/comic.h b/applets/comic/engine/comic.h new file mode 100644 index 00000000..0cdb1e3f --- /dev/null +++ b/applets/comic/engine/comic.h @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2007 Tobias Koenig + * SPDX-FileCopyrightText: 2022 Alexander Lohnau + * + * SPDX-License-Identifier: LGPL-2.0-only + */ + +#ifndef COMIC_ENGINE_H +#define COMIC_ENGINE_H + +// Qt +#include +#include +#include +#include + +#include "types.h" + +class ComicProvider; + +/** + * This class provides the comic strip. + * + * The query keys have the following structure: + * \:\ + * usually the suffix is the date + * e.g. + * userfriendly:2007-07-19 + * but some other comics uses numerical identifiers, like + * xkcd:378 + * if the suffix is empty the latest comic will be returned + * + */ +class ComicEngine : public QObject +{ + Q_OBJECT + +public: + ComicEngine(QObject *parent); + + QList loadProviders(); + + void setMaxComicLimit(int maxComicLimit); + bool requestSource(const QString &identifier); + +Q_SIGNALS: + void requestFinished(const ComicMetaData &data); + +private: + void finished(ComicProvider *); + void error(ComicProvider *); + ComicMetaData metaDataFromProvider(ComicProvider *provider); + QString lastCachedIdentifier(const QString &identifier) const; + bool isOnline() const; + +private: + bool mEmptySuffix; + QString mIdentifierError; + QHash m_jobs; + QSet mProviders; +}; + +#endif diff --git a/applets/comic/engine/comic_package.cpp b/applets/comic/engine/comic_package.cpp new file mode 100644 index 00000000..2f7a519d --- /dev/null +++ b/applets/comic/engine/comic_package.cpp @@ -0,0 +1,43 @@ +/* + * SPDX-FileCopyrightText: 2008 Petri Damstén + * SPDX-FileCopyrightText: 2023 Alexander Lohnau +#include +#include +#include +#include + +class ComicPackage : public KPackage::PackageStructure +{ + Q_OBJECT +public: + using KPackage::PackageStructure::PackageStructure; + void initPackage(KPackage::Package *package) override + { + package->addDirectoryDefinition("images", QStringLiteral("images")); + package->setMimeTypes("images", QStringList{QStringLiteral("image/svg+xml"), QStringLiteral("image/png"), QStringLiteral("image/jpeg")}); + + package->addDirectoryDefinition("scripts", QStringLiteral("code")); + package->setMimeTypes("scripts", QStringList{QStringLiteral("text/*")}); + + package->addFileDefinition("mainscript", QStringLiteral("code/main.js")); + package->addFileDefinition("mainscript", QStringLiteral("code/main.es")); + package->addFileDefinition("metadata", QStringLiteral("metadata.desktop")); + package->setRequired("metadata", true); + package->setRequired("mainscript", true); + package->setDefaultPackageRoot(QStringLiteral("plasma/comics/")); + } + void pathChanged(KPackage::Package *package) override + { + QMap extra{{QStringLiteral("X-KDE-PlasmaComicProvider-SuffixType"), QMetaType::QString}}; + KPackagePrivate::convertCompatMetaDataDesktopFile(package, extra); + } +}; + +K_PLUGIN_CLASS_WITH_JSON(ComicPackage, "plasma-packagestructure-comic.json") + +#include "comic_package.moc" diff --git a/applets/comic/engine/comicprovider.cpp b/applets/comic/engine/comicprovider.cpp new file mode 100644 index 00000000..8bf60cdd --- /dev/null +++ b/applets/comic/engine/comicprovider.cpp @@ -0,0 +1,319 @@ +/* + * SPDX-FileCopyrightText: 2007 Tobias Koenig + * SPDX-FileCopyrightText: 2022 Alexander Lohnau + * + * SPDX-License-Identifier: LGPL-2.0-only + */ + +#include "comicprovider.h" +#include "comic_debug.h" + +#include +#include + +#include +#include + +class ComicProvider::Private +{ +public: + Private(ComicProvider *parent, const KPluginMetaData &data, IdentifierType suffixType) + : mParent(parent) + , mIsCurrent(false) + , mFirstStripNumber(1) + , mComicDescription(data) + , mSuffixType(suffixType) + { + mTimer = new QTimer(parent); + mTimer->setSingleShot(true); + mTimer->setInterval(60000); // timeout after 1 minute + connect(mTimer, &QTimer::timeout, mParent, [this]() { + // operation took too long, abort it + Q_EMIT mParent->error(mParent); + }); + } + + void jobDone(KJob *job) + { + if (job->error()) { + mParent->pageError(job->property("uid").toInt(), job->errorText()); + } else { + KIO::StoredTransferJob *storedJob = qobject_cast(job); + mParent->pageRetrieved(job->property("uid").toInt(), storedJob->data()); + } + } + + void slotRedirection(KIO::Job *job, const QUrl &oldUrl, const QUrl &newUrl) + { + Q_UNUSED(oldUrl) + + mParent->redirected(job->property("uid").toInt(), newUrl); + mRedirections.remove(job); + } + + void slotRedirectionDone(KJob *job) + { + if (job->error()) { + qCDebug(PLASMA_COMIC) << "Redirection job with id" << job->property("uid").toInt() << "finished with an error."; + } + + if (mRedirections.contains(job)) { + // no redirection took place, return the original url + mParent->redirected(job->property("uid").toInt(), mRedirections[job]); + mRedirections.remove(job); + } + } + + ComicProvider *mParent; + QString mRequestedId; + QString mRequestedComicName; + QString mComicAuthor; + QUrl mImageUrl; + bool mIsCurrent; + bool mIsLeftToRight; + bool mIsTopToBottom; + QDate mRequestedDate; + QDate mFirstStripDate; + int mRequestedNumber; + int mFirstStripNumber; + const KPluginMetaData mComicDescription; + QTimer *mTimer; + QHash mRedirections; + const IdentifierType mSuffixType; +}; + +ComicProvider::ComicProvider(QObject *parent, const KPluginMetaData &data, IdentifierType type, const QVariant &identifier) + : QObject(parent) + , d(new Private(this, data, type)) +{ + if (type == IdentifierType::DateIdentifier) { + d->mRequestedDate = identifier.toDate(); + } else if (type == IdentifierType::NumberIdentifier) { + d->mRequestedNumber = identifier.toInt(); + } else if (type == IdentifierType::StringIdentifier) { + d->mRequestedId = identifier.toString(); + + int index = d->mRequestedId.indexOf(QLatin1Char(':')); + d->mRequestedComicName = d->mRequestedId.mid(0, index); + } else { + qFatal("Invalid type passed to comic provider"); + } + + d->mTimer->start(); + connect(this, &ComicProvider::finished, this, [this]() { + // everything finished, stop the timeout timer + d->mTimer->stop(); + }); +} + +ComicProvider::~ComicProvider() +{ + delete d; +} + +QString ComicProvider::nextIdentifier() const +{ + if (identifierType() == IdentifierType::DateIdentifier && d->mRequestedDate != QDate::currentDate()) { + return d->mRequestedDate.addDays(1).toString(Qt::ISODate); + } + + return QString(); +} + +QString ComicProvider::previousIdentifier() const +{ + if ((identifierType() == IdentifierType::DateIdentifier) && (!firstStripDate().isValid() || d->mRequestedDate > firstStripDate())) { + return d->mRequestedDate.addDays(-1).toString(Qt::ISODate); + } + + return QString(); +} + +QString ComicProvider::stripTitle() const +{ + return QString(); +} + +QString ComicProvider::additionalText() const +{ + return QString(); +} + +void ComicProvider::setIsCurrent(bool value) +{ + d->mIsCurrent = value; +} + +bool ComicProvider::isCurrent() const +{ + return d->mIsCurrent; +} + +QDate ComicProvider::requestedDate() const +{ + return d->mRequestedDate; +} + +QDate ComicProvider::firstStripDate() const +{ + return d->mFirstStripDate; +} + +QString ComicProvider::comicAuthor() const +{ + return d->mComicAuthor; +} + +void ComicProvider::setComicAuthor(const QString &author) +{ + d->mComicAuthor = author; +} + +void ComicProvider::setFirstStripDate(const QDate &date) +{ + d->mFirstStripDate = date; +} + +int ComicProvider::firstStripNumber() const +{ + return d->mFirstStripNumber; +} + +void ComicProvider::setFirstStripNumber(int number) +{ + d->mFirstStripNumber = number; +} + +QString ComicProvider::firstStripIdentifier() const +{ + if ((identifierType() == IdentifierType::DateIdentifier) && d->mFirstStripDate.isValid()) { + return d->mFirstStripDate.toString(Qt::ISODate); + } else if (identifierType() == IdentifierType::NumberIdentifier) { + return QString::number(d->mFirstStripNumber); + } + + return QString(); +} + +int ComicProvider::requestedNumber() const +{ + return d->mRequestedNumber; +} + +QString ComicProvider::requestedString() const +{ + return d->mRequestedId; +} + +QString ComicProvider::requestedComicName() const +{ + return d->mRequestedComicName; +} + +void ComicProvider::requestPage(const QUrl &url, int id, const MetaInfos &infos) +{ + qCDebug(PLASMA_COMIC) << "Requested page" << url << "with id" << id << "and additional metadata" << infos; + // each request restarts the timer + d->mTimer->start(); + + if (id == Image) { + d->mImageUrl = url; + } + + KIO::StoredTransferJob *job; + if (id == Image) { + // use cached information for the image if available + job = KIO::storedGet(url, KIO::NoReload, KIO::HideProgressInfo); + } else { + // for webpages we always reload, making sure, that changes are recognised + job = KIO::storedGet(url, KIO::Reload, KIO::HideProgressInfo); + } + job->setProperty("uid", id); + connect(job, &KJob::result, this, [this](KJob *job) { + d->jobDone(job); + }); + + if (!infos.isEmpty()) { + QMapIterator it(infos); + while (it.hasNext()) { + it.next(); + job->addMetaData(it.key(), it.value()); + } + } +} + +void ComicProvider::requestRedirectedUrl(const QUrl &url, int id, const MetaInfos &infos) +{ + // each request restarts the timer + d->mTimer->start(); + + KIO::MimetypeJob *job = KIO::mimetype(url, KIO::HideProgressInfo); + job->setProperty("uid", id); + d->mRedirections[job] = url; + connect(job, &KIO::MimetypeJob::redirection, this, [this](KIO::Job *job, const QUrl &newUrl) { + d->slotRedirection(job, QUrl(), newUrl); + }); + connect(job, &KIO::MimetypeJob::permanentRedirection, this, [this](KIO::Job *job, const QUrl &oldUrl, const QUrl &newUrl) { + d->slotRedirection(job, oldUrl, newUrl); + }); + connect(job, &KIO::MimetypeJob::result, this, [this](KJob *job) { + d->slotRedirectionDone(job); + }); + + if (!infos.isEmpty()) { + QMapIterator it(infos); + while (it.hasNext()) { + it.next(); + job->addMetaData(it.key(), it.value()); + } + } +} + +void ComicProvider::pageRetrieved(int, const QByteArray &) +{ +} + +void ComicProvider::pageError(int, const QString &) +{ +} + +void ComicProvider::redirected(int, const QUrl &) +{ +} + +QString ComicProvider::pluginName() const +{ + return d->mComicDescription.pluginId(); +} + +QString ComicProvider::name() const +{ + return d->mComicDescription.name(); +} + +KPluginMetaData ComicProvider::description() const +{ + return d->mComicDescription; +} + +QUrl ComicProvider::shopUrl() const +{ + return QUrl(); +} + +QUrl ComicProvider::imageUrl() const +{ + return d->mImageUrl; +} + +bool ComicProvider::isLeftToRight() const +{ + return true; +} + +bool ComicProvider::isTopToBottom() const +{ + return true; +} + +#include "moc_comicprovider.cpp" diff --git a/applets/comic/engine/comicprovider.h b/applets/comic/engine/comicprovider.h new file mode 100644 index 00000000..46ad73be --- /dev/null +++ b/applets/comic/engine/comicprovider.h @@ -0,0 +1,264 @@ +/* + * SPDX-FileCopyrightText: 2007 Tobias Koenig + * SPDX-FileCopyrightText: 2022 Alexander Lohnau + * + * SPDX-License-Identifier: LGPL-2.0-only + */ + +#ifndef COMICPROVIDER_H +#define COMICPROVIDER_H + +#include +#include +#include + +#include "types.h" + +class QImage; +class QUrl; + +/** + * This class is an interface for comic providers. + */ +class ComicProvider : public QObject +{ + Q_OBJECT + +public: + enum RequestType { + Page = 0, + Image, + User, + }; + + /** + * Creates a new comic provider. + * + * @param parent The parent object. + * @param args Arguments passed by the plugin loader. + */ + ComicProvider(QObject *parent, const KPluginMetaData &data, IdentifierType type, const QVariant &identifier); + + /** + * Destroys the comic provider. + */ + ~ComicProvider() override; + + /** + * Returns the type of identifier that is used by this + * comic provider. + */ + virtual IdentifierType identifierType() const = 0; + + /** + * Returns the url of the website where the comic of that particular date resides. + */ + virtual QUrl websiteUrl() const = 0; + + /** + * Returns the direct url to the comic, if the comic strip is a combination of multiple + * images, then this should return the url to one part of it + * @note the image url is automatically set by requestPage with the ComicProvider::Image id + * @see requestPage + */ + virtual QUrl imageUrl() const; + + /** + * Returns the url of the website where the comic has a shop. + */ + virtual QUrl shopUrl() const; + + /** + * Returns the requested image. + * + * Note: This method returns only a valid image after the + * finished() signal has been emitted. + */ + virtual QImage image() const = 0; + + /** + * Returns the identifier of the comic request. + */ + virtual QString identifier() const = 0; + + /** + * Returns the identifier of the next comic (default: date of next day). + */ + virtual QString nextIdentifier() const; + + /** + * Returns the identifier of the previous comic (default: date of previous day + * as long). + */ + virtual QString previousIdentifier() const; + + /** + * Returns the identifier of the first strip. + */ + virtual QString firstStripIdentifier() const; + + /** + * Returns the author of the comic. + */ + virtual QString comicAuthor() const; + + /** + * Returns the title of the strip. + */ + virtual QString stripTitle() const; + + /** + * Returns additionalText of the comic. + */ + virtual QString additionalText() const; + + /** + * Returns the identifier for the comic + */ + virtual QString pluginName() const; + + /** + * Returns the name for the comic + */ + virtual QString name() const; + + /** + * Returns whether the comic is leftToRight or not + */ + virtual bool isLeftToRight() const; + + /** + * Returns whether the comic is topToBottom or not + */ + virtual bool isTopToBottom() const; + + /** + * Returns the plugin info for the comic + */ + KPluginMetaData description() const; + + /** + * Set whether this request is for the current comic (only used internally). + */ + void setIsCurrent(bool value); + + /** + * Returns whether this request is for the current comic (only used internally). + */ + bool isCurrent() const; + +Q_SIGNALS: + /** + * This signal is emitted whenever a request has been finished + * successfully. + * + * @param provider The provider which emitted the signal. + */ + void finished(ComicProvider *provider); + + /** + * This signal is emitted whenever an error has occurred. + * + * @param provider The provider which emitted the signal. + */ + void error(ComicProvider *provider); + +protected: + /** + * Returns the date identifier that was requested by the applet. + */ + QDate requestedDate() const; + + /** + * Returns the numeric identifier that was requested by the applet. + */ + int requestedNumber() const; + + /** + * Returns the string identifier that was requested by the applet. + */ + QString requestedString() const; + + /** + * @internal + * + * Returns the comic name of the string identifier that was requested by the applet. + */ + QString requestedComicName() const; + + /** + * Returns the date of the first available comic strip. + */ + QDate firstStripDate() const; + + /** + * Sets the date of the first available comic strip. + */ + void setFirstStripDate(const QDate &date); + + /** + * Returns the number of the first available comic strip (default: 1). + */ + int firstStripNumber() const; + + /** + * Sets the number of the first available comic strip. + */ + void setFirstStripNumber(int number); + + /** + * Sets the name of the comic author. + */ + void setComicAuthor(const QString &author); + + typedef QMap MetaInfos; + + /** + * This method should be used by all comic providers to request + * websites or images from the web. It encapsulates the HTTP + * handling and calls pageRetrieved() or pageError() on success or error. + * + * @param url The url to access. + * @param id A unique id that identifies this request. + * @param infos A list of meta information passed to http. + */ + void requestPage(const QUrl &url, int id, const MetaInfos &infos = MetaInfos()); + + /** + * This method can be used to find the place url points to, when finished + * urlRetrieved() is called, either with the original url or a redirected url + * @param url to check for redirections + * @param id A unique id that identifies this request. + * @param infos A list of meta information passed to KIO. + */ + void requestRedirectedUrl(const QUrl &url, int id, const MetaInfos &infos = MetaInfos()); + + /** + * This method is called whenever a request done by requestPage() was successful. + * + * @param id The unique identifier of that request. + * @param data The data of the fetched object. + */ + virtual void pageRetrieved(int id, const QByteArray &data); + + /** + * This method is called whenever a request done by requestPage() has failed. + * + * @param id The unique identifier of that request. + * @param message The error message. + */ + virtual void pageError(int id, const QString &message); + + /** + * This method is called whenever a request by requestRedirectedUrl() was done + * @param id The unique identifier of that request. + * @param newUrl The redirected Url + */ + virtual void redirected(int id, const QUrl &newUrl); + +private: + class Private; + Private *const d; +}; + +#endif diff --git a/applets/comic/engine/comicproviderkross.cpp b/applets/comic/engine/comicproviderkross.cpp new file mode 100644 index 00000000..eca0a4f3 --- /dev/null +++ b/applets/comic/engine/comicproviderkross.cpp @@ -0,0 +1,107 @@ +/* + * SPDX-FileCopyrightText: 2008 Petri Damstén + * SPDX-FileCopyrightText: 2022 Alexander Lohnau + * + * SPDX-License-Identifier: LGPL-2.0-only + */ + +#include "comicproviderkross.h" + +ComicProviderKross::ComicProviderKross(QObject *parent, const KPluginMetaData &data, IdentifierType type, const QVariant &identifier) + : ComicProvider(parent, data, type, identifier) + , m_wrapper(this) +{ +} + +ComicProviderKross::~ComicProviderKross() +{ +} + +bool ComicProviderKross::isLeftToRight() const +{ + return m_wrapper.isLeftToRight(); +} + +bool ComicProviderKross::isTopToBottom() const +{ + return m_wrapper.isTopToBottom(); +} + +IdentifierType ComicProviderKross::identifierType() const +{ + return m_wrapper.identifierType(); +} + +QUrl ComicProviderKross::websiteUrl() const +{ + return QUrl(m_wrapper.websiteUrl()); +} + +QUrl ComicProviderKross::shopUrl() const +{ + return QUrl(m_wrapper.shopUrl()); +} + +QImage ComicProviderKross::image() const +{ + return m_wrapper.comicImage(); +} + +QString ComicProviderKross::identifierToString(const QVariant &identifier) const +{ + QString result; + + if (!identifier.isNull() && identifier.typeId() != QMetaType::Bool) { + if (identifierType() == IdentifierType::DateIdentifier) { + result = identifier.toDate().toString(Qt::ISODate); + } else { + result = identifier.toString(); + } + } + return result; +} + +QString ComicProviderKross::identifier() const +{ + return pluginName() + QLatin1Char(':') + identifierToString(m_wrapper.identifierVariant()); +} + +QString ComicProviderKross::nextIdentifier() const +{ + return identifierToString(m_wrapper.nextIdentifierVariant()); +} + +QString ComicProviderKross::previousIdentifier() const +{ + return identifierToString(m_wrapper.previousIdentifierVariant()); +} + +QString ComicProviderKross::firstStripIdentifier() const +{ + return identifierToString(m_wrapper.firstIdentifierVariant()); +} + +QString ComicProviderKross::stripTitle() const +{ + return m_wrapper.title(); +} + +QString ComicProviderKross::additionalText() const +{ + return m_wrapper.additionalText(); +} + +void ComicProviderKross::pageRetrieved(int id, const QByteArray &data) +{ + m_wrapper.pageRetrieved(id, data); +} + +void ComicProviderKross::pageError(int id, const QString &message) +{ + m_wrapper.pageError(id, message); +} + +void ComicProviderKross::redirected(int id, const QUrl &newUrl) +{ + m_wrapper.redirected(id, newUrl); +} diff --git a/applets/comic/engine/comicproviderkross.h b/applets/comic/engine/comicproviderkross.h new file mode 100644 index 00000000..ea4d8c12 --- /dev/null +++ b/applets/comic/engine/comicproviderkross.h @@ -0,0 +1,50 @@ +/* + * SPDX-FileCopyrightText: 2008 Petri Damstén + * SPDX-FileCopyrightText: 2022 Alexander Lohnau + * + * SPDX-License-Identifier: LGPL-2.0-only + */ + +#ifndef COMICPROVIDERKROSS_H +#define COMICPROVIDERKROSS_H + +#include "comicprovider.h" +#include "comicproviderwrapper.h" +#include "types.h" + +#include +#include + +class ComicProviderKross : public ComicProvider +{ + friend class ComicProviderWrapper; + Q_OBJECT + +public: + ComicProviderKross(QObject *parent, const KPluginMetaData &data, IdentifierType type, const QVariant &identifier); + ~ComicProviderKross() override; + + bool isLeftToRight() const override; + bool isTopToBottom() const override; + IdentifierType identifierType() const override; + QUrl websiteUrl() const override; + QUrl shopUrl() const override; + QImage image() const override; + QString identifier() const override; + QString nextIdentifier() const override; + QString previousIdentifier() const override; + QString firstStripIdentifier() const override; + QString stripTitle() const override; + QString additionalText() const override; + +protected: + void pageRetrieved(int id, const QByteArray &data) override; + void pageError(int id, const QString &message) override; + void redirected(int id, const QUrl &newUrl) override; + QString identifierToString(const QVariant &identifier) const; + +private: + mutable ComicProviderWrapper m_wrapper; +}; + +#endif diff --git a/applets/comic/engine/comicproviderwrapper.cpp b/applets/comic/engine/comicproviderwrapper.cpp new file mode 100644 index 00000000..678728b5 --- /dev/null +++ b/applets/comic/engine/comicproviderwrapper.cpp @@ -0,0 +1,848 @@ +/* + * SPDX-FileCopyrightText: 2008 Petri Damstén + * SPDX-FileCopyrightText: 2010 Matthias Fuchs + * + * SPDX-License-Identifier: LGPL-2.0-only + */ + +#include "comicproviderwrapper.h" +#include "comic_debug.h" +#include "comicproviderkross.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QStringList ComicProviderWrapper::mExtensions; + +ImageWrapper::ImageWrapper(QObject *parent, const QByteArray &data) + : QObject(parent) + , mImage(QImage::fromData(data)) + , mRawData(data) +{ + resetImageReader(); +} + +QImage ImageWrapper::image() const +{ + return mImage; +} + +void ImageWrapper::setImage(const QImage &image) +{ + mImage = image; + mRawData.clear(); + + resetImageReader(); +} + +QByteArray ImageWrapper::rawData() const +{ + if (mRawData.isNull()) { + QBuffer buffer(&mRawData); + mImage.save(&buffer); + } + + return mRawData; +} + +void ImageWrapper::setRawData(const QByteArray &rawData) +{ + mRawData = rawData; + mImage = QImage::fromData(mRawData); + + resetImageReader(); +} + +void ImageWrapper::resetImageReader() +{ + if (mBuffer.isOpen()) { + mBuffer.close(); + } + rawData(); // to update the rawData if needed + mBuffer.setBuffer(&mRawData); + mBuffer.open(QIODevice::ReadOnly); + mImageReader.setDevice(&mBuffer); +} + +int ImageWrapper::imageCount() const +{ + return mImageReader.imageCount(); +} + +QImage ImageWrapper::read() +{ + return mImageReader.read(); +} + +DateWrapper::DateWrapper(const QDate &date) + : mDate(date) +{ +} + +QDate DateWrapper::date() const +{ + return mDate; +} + +void DateWrapper::setDate(const QDate &date) +{ + mDate = date; +} + +QDate DateWrapper::fromVariant(const QVariant &variant) +{ + if (variant.typeId() == QMetaType::QDate || variant.typeId() == QMetaType::QDateTime) { + return variant.toDate(); + } else if (variant.typeId() == QMetaType::QString) { + return QDate::fromString(variant.toString(), Qt::ISODate); + } else { + if (variant.canConvert()) { + return variant.value().date(); + } + } + return QDate(); +} + +DateWrapper DateWrapper::addDays(int ndays) +{ + return DateWrapper(mDate.addDays(ndays)); +} + +DateWrapper DateWrapper::addMonths(int nmonths) +{ + return DateWrapper(mDate.addMonths(nmonths)); +} + +DateWrapper DateWrapper::addYears(int nyears) +{ + return DateWrapper(mDate.addYears(nyears)); +} + +int DateWrapper::day() const +{ + return mDate.day(); +} + +int DateWrapper::dayOfWeek() const +{ + return mDate.dayOfWeek(); +} + +int DateWrapper::dayOfYear() const +{ + return mDate.dayOfYear(); +} + +int DateWrapper::daysInMonth() const +{ + return mDate.daysInMonth(); +} + +int DateWrapper::daysInYear() const +{ + return mDate.daysInYear(); +} + +int DateWrapper::daysTo(const QVariant d) const +{ + return mDate.daysTo(fromVariant(d)); +} + +bool DateWrapper::isNull() const +{ + return mDate.isNull(); +} + +bool DateWrapper::isValid() const +{ + return mDate.isValid(); +} + +int DateWrapper::month() const +{ + return mDate.month(); +} + +bool DateWrapper::setDate(int year, int month, int day) +{ + return mDate.setDate(year, month, day); +} + +int DateWrapper::toJulianDay() const +{ + return mDate.toJulianDay(); +} + +QString DateWrapper::toString(const QString &format) const +{ + return mDate.toString(format); +} + +QString DateWrapper::toString(int format) const +{ + return mDate.toString((Qt::DateFormat)format); +} + +int DateWrapper::weekNumber() const +{ + return mDate.weekNumber(); +} + +int DateWrapper::year() const +{ + return mDate.year(); +} + +StaticDateWrapper::StaticDateWrapper(QObject *parent) + : QObject(parent) +{ +} + +DateWrapper StaticDateWrapper::currentDate() +{ + return DateWrapper(QDate::currentDate()); +} + +DateWrapper StaticDateWrapper::fromJulianDay(int jd) +{ + return DateWrapper(QDate::fromJulianDay(jd)); +} + +DateWrapper StaticDateWrapper::fromString(const QString &string, int format) +{ + return DateWrapper(QDate::fromString(string, (Qt::DateFormat)format)); +} + +DateWrapper StaticDateWrapper::fromString(const QString &string, const QString &format) +{ + return DateWrapper(QDate::fromString(string, format)); +} + +bool StaticDateWrapper::isLeapYear(int year) +{ + return QDate::isLeapYear(year); +} + +bool StaticDateWrapper::isValid(int year, int month, int day) +{ + return QDate::isValid(year, month, day); +} + +QString StaticDateWrapper::longDayName(int weekday) +{ + return QLocale::system().dayName(weekday, QLocale::LongFormat); +} + +QString StaticDateWrapper::longMonthName(int month) +{ + return QLocale::system().monthName(month, QLocale::LongFormat); +} + +QString StaticDateWrapper::shortDayName(int weekday) +{ + return QLocale::system().dayName(weekday, QLocale::ShortFormat); +} + +QString StaticDateWrapper::shortMonthName(int month) +{ + return QLocale::system().monthName(month, QLocale::ShortFormat); +} + +ComicProviderWrapper::ComicProviderWrapper(ComicProviderKross *parent) + : QObject(parent) + , mProvider(parent) + , mKrossImage(nullptr) + , mPackage(nullptr) + , mRequests(0) + , mIdentifierSpecified(false) + , mIsLeftToRight(true) + , mIsTopToBottom(true) +{ + QTimer::singleShot(0, this, &ComicProviderWrapper::init); +} + +ComicProviderWrapper::~ComicProviderWrapper() +{ + delete mPackage; +} + +void ComicProviderWrapper::init() +{ + const QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, + QLatin1String("plasma/comics/") + mProvider->pluginName() + QLatin1Char('/'), + QStandardPaths::LocateDirectory); + qCDebug(PLASMA_COMIC) << "ComicProviderWrapper::init() package is" << mProvider->pluginName() << " at " << path; + + if (!path.isEmpty()) { + mPackage = new KPackage::Package(KPackage::PackageLoader::self()->loadPackageStructure(QStringLiteral("Plasma/Comic"))); + mPackage->setPath(path); + + if (mPackage->isValid()) { + m_engine = new QJSEngine(this); + QString mainscript = mPackage->filePath("mainscript"); + QFile f(mainscript); + if (f.open(QFile::ReadOnly)) { + m_engine->globalObject().setProperty("Comic", m_engine->newQMetaObject(&ComicProviderWrapper::staticMetaObject)); + auto obj = m_engine->newQObject(this); + + // If we set the comic in the global object we can not access the staticMetaObject + // consequently the values have to be written manually + obj.setProperty("Page", ComicProvider::Page); + obj.setProperty("Image", ComicProvider::Image); + obj.setProperty("User", ComicProvider::User); + obj.setProperty("Left", ComicProviderWrapper::Left); + obj.setProperty("Top", ComicProviderWrapper::Top); + obj.setProperty("Right", ComicProviderWrapper::Right); + obj.setProperty("Bottom", ComicProviderWrapper::Bottom); + obj.setProperty("DateIdentifier", (int)IdentifierType::DateIdentifier); + obj.setProperty("NumberIdentifier", (int)IdentifierType::NumberIdentifier); + obj.setProperty("StringIdentifier", (int)IdentifierType::StringIdentifier); + + m_engine->globalObject().setProperty("comic", obj); + m_engine->globalObject().setProperty("date", m_engine->newQObject(new StaticDateWrapper(this))); + m_engine->evaluate("var print = comic.print"); + mIdentifierSpecified = !mProvider->isCurrent(); + m_engine->evaluate(f.readAll(), mainscript); + QJSValueIterator it(m_engine->globalObject()); + while (it.hasNext()) { + it.next(); + if (it.value().isCallable()) { + mFunctions << it.name(); + } + } + setIdentifierToDefault(); + callFunction(QStringLiteral("init")); + } + } + } +} + +IdentifierType ComicProviderWrapper::identifierType() const +{ + IdentifierType result = IdentifierType::StringIdentifier; + const QString type = mProvider->description().value(QLatin1String("X-KDE-PlasmaComicProvider-SuffixType")); + if (type == QLatin1String("Date")) { + result = IdentifierType::DateIdentifier; + } else if (type == QLatin1String("Number")) { + result = IdentifierType::NumberIdentifier; + } else if (type == QLatin1String("String")) { + result = IdentifierType::StringIdentifier; + } + return result; +} + +QImage ComicProviderWrapper::comicImage() +{ + ImageWrapper *img = qobject_cast(callFunction(QLatin1String("image")).value()); + if (functionCalled() && img) { + return img->image(); + } + if (mKrossImage) { + return mKrossImage->image(); + } + return QImage(); +} + +QJSValue ComicProviderWrapper::identifierToScript(const QVariant &identifier) +{ + if (identifierType() == IdentifierType::DateIdentifier && identifier.typeId() != QMetaType::Bool) { + return m_engine->toScriptValue(DateWrapper(identifier.toDate())); + } + return m_engine->toScriptValue(identifier); +} + +QVariant ComicProviderWrapper::identifierFromScript(const QJSValue &identifier) const +{ + if (identifier.toVariant().canConvert()) { + return identifier.toVariant().value().date(); + } + return identifier.toVariant(); +} + +void ComicProviderWrapper::checkIdentifier(QVariant *identifier) +{ + switch (identifierType()) { + case IdentifierType::DateIdentifier: + if (!mLastIdentifier.isNull() && !identifier->isNull() && (!mIdentifierSpecified || identifier->toDate() > mLastIdentifier.toDate())) { + *identifier = mLastIdentifier; + } + if (!mFirstIdentifier.isNull() && !identifier->isNull() && identifier->toDate() < mFirstIdentifier.toDate()) { + *identifier = mFirstIdentifier; + } + break; + case IdentifierType::NumberIdentifier: + if (!mLastIdentifier.isNull() && !identifier->isNull() && (!mIdentifierSpecified || identifier->toInt() > mLastIdentifier.toInt())) { + *identifier = mLastIdentifier; + } + if (!mFirstIdentifier.isNull() && !identifier->isNull() && identifier->toInt() < mFirstIdentifier.toInt()) { + *identifier = mFirstIdentifier; + } + break; + case IdentifierType::StringIdentifier: + if (!mLastIdentifier.isNull() && !mLastIdentifier.toString().isEmpty() && !mIdentifierSpecified) { + *identifier = mLastIdentifier; + } + break; + } +} + +void ComicProviderWrapper::setIdentifierToDefault() +{ + switch (identifierType()) { + case IdentifierType::DateIdentifier: + mIdentifier = mProvider->requestedDate(); + mLastIdentifier = QDate::currentDate(); + break; + case IdentifierType::NumberIdentifier: + mIdentifier = mProvider->requestedNumber(); + mFirstIdentifier = 1; + break; + case IdentifierType::StringIdentifier: + mIdentifier = mProvider->requestedString(); + break; + } +} + +bool ComicProviderWrapper::identifierSpecified() const +{ + return mIdentifierSpecified; +} + +bool ComicProviderWrapper::isLeftToRight() const +{ + return mIsLeftToRight; +} + +void ComicProviderWrapper::setLeftToRight(bool ltr) +{ + mIsLeftToRight = ltr; +} + +bool ComicProviderWrapper::isTopToBottom() const +{ + return mIsTopToBottom; +} + +void ComicProviderWrapper::setTopToBottom(bool ttb) +{ + mIsTopToBottom = ttb; +} + +QString ComicProviderWrapper::textCodec() const +{ + return QString::fromLatin1(mTextCodec); +} + +void ComicProviderWrapper::setTextCodec(const QString &textCodec) +{ + mTextCodec = textCodec.toLatin1(); +} + +QString ComicProviderWrapper::comicAuthor() const +{ + return mProvider->comicAuthor(); +} + +void ComicProviderWrapper::setComicAuthor(const QString &author) +{ + mProvider->setComicAuthor(author); +} + +QString ComicProviderWrapper::websiteUrl() const +{ + return mWebsiteUrl; +} + +void ComicProviderWrapper::setWebsiteUrl(const QString &websiteUrl) +{ + mWebsiteUrl = websiteUrl; +} + +QString ComicProviderWrapper::shopUrl() const +{ + return mShopUrl; +} + +void ComicProviderWrapper::setShopUrl(const QString &shopUrl) +{ + mShopUrl = shopUrl; +} + +QString ComicProviderWrapper::title() const +{ + return mTitle; +} + +void ComicProviderWrapper::setTitle(const QString &title) +{ + mTitle = title; +} + +QString ComicProviderWrapper::additionalText() const +{ + return mAdditionalText; +} + +void ComicProviderWrapper::setAdditionalText(const QString &additionalText) +{ + mAdditionalText = additionalText; +} + +QJSValue ComicProviderWrapper::identifier() +{ + return identifierToScript(mIdentifier); +} + +void ComicProviderWrapper::setIdentifier(const QJSValue &identifier) +{ + mIdentifier = identifierFromScript(identifier); + checkIdentifier(&mIdentifier); +} + +QJSValue ComicProviderWrapper::nextIdentifier() +{ + return identifierToScript(mNextIdentifier); +} + +void ComicProviderWrapper::setNextIdentifier(const QJSValue &nextIdentifier) +{ + mNextIdentifier = identifierFromScript(nextIdentifier); + if (mNextIdentifier == mIdentifier) { + mNextIdentifier.clear(); + qCWarning(PLASMA_COMIC) << "Next identifier is the same as the current one, clearing next identifier."; + } +} + +QJSValue ComicProviderWrapper::previousIdentifier() +{ + return identifierToScript(mPreviousIdentifier); +} + +void ComicProviderWrapper::setPreviousIdentifier(const QJSValue &previousIdentifier) +{ + mPreviousIdentifier = identifierFromScript(previousIdentifier); + if (mPreviousIdentifier == mIdentifier) { + mPreviousIdentifier.clear(); + qCWarning(PLASMA_COMIC) << "Previous identifier is the same as the current one, clearing previous identifier."; + } +} + +QJSValue ComicProviderWrapper::firstIdentifier() +{ + return identifierToScript(mFirstIdentifier); +} + +void ComicProviderWrapper::setFirstIdentifier(const QJSValue &firstIdentifier) +{ + switch (identifierType()) { + case IdentifierType::DateIdentifier: + mProvider->setFirstStripDate(DateWrapper::fromVariant(QVariant::fromValue(firstIdentifier.toQObject()))); + break; + case IdentifierType::NumberIdentifier: + mProvider->setFirstStripNumber(firstIdentifier.toInt()); + break; + case IdentifierType::StringIdentifier: + break; + } + mFirstIdentifier = identifierFromScript(firstIdentifier); + checkIdentifier(&mIdentifier); +} + +QJSValue ComicProviderWrapper::lastIdentifier() +{ + return identifierToScript(mLastIdentifier); +} + +void ComicProviderWrapper::setLastIdentifier(const QJSValue &lastIdentifier) +{ + mLastIdentifier = identifierFromScript(lastIdentifier); + checkIdentifier(&mIdentifier); +} + +QVariant ComicProviderWrapper::identifierVariant() const +{ + return mIdentifier; +} + +QVariant ComicProviderWrapper::firstIdentifierVariant() const +{ + return mFirstIdentifier; +} + +QVariant ComicProviderWrapper::lastIdentifierVariant() const +{ + return mLastIdentifier; +} + +QVariant ComicProviderWrapper::nextIdentifierVariant() const +{ + // either handle both previousIdentifier and nextIdentifier or handle none + if (mPreviousIdentifier.isNull() && mNextIdentifier.isNull()) { + switch (identifierType()) { + case IdentifierType::DateIdentifier: + if ((mLastIdentifier.isNull() && mIdentifier.toDate() < QDate::currentDate()) + || (!mLastIdentifier.isNull() && mIdentifier.toDate() < mLastIdentifier.toDate())) { + return mIdentifier.toDate().addDays(1); + } else { + return false; + } + case IdentifierType::NumberIdentifier: + if (mLastIdentifier.isNull() || mIdentifier.toInt() < mLastIdentifier.toInt()) { + return mIdentifier.toInt() + 1; + } else { + return false; + } + case IdentifierType::StringIdentifier: + break; + } + // check if the nextIdentifier is correct + } else if (!mNextIdentifier.isNull()) { + // no nextIdentifier if mIdentifier == mLastIdentifier or if no identifier has been specified + switch (identifierType()) { + case IdentifierType::DateIdentifier: + if ((!mLastIdentifier.isNull() && (mIdentifier.toDate() == mLastIdentifier.toDate())) || !mIdentifierSpecified) { + return false; + } + break; + case IdentifierType::NumberIdentifier: + if ((!mLastIdentifier.isNull() && (mIdentifier.toInt() == mLastIdentifier.toInt())) || !mIdentifierSpecified) { + return false; + } + break; + case IdentifierType::StringIdentifier: + if (!mIdentifierSpecified) { + return false; + } + break; + } + } + return mNextIdentifier; +} + +QVariant ComicProviderWrapper::previousIdentifierVariant() const +{ + // either handle both previousIdentifier and nextIdentifier or handle none + if (mPreviousIdentifier.isNull() && mNextIdentifier.isNull()) { + switch (identifierType()) { + case IdentifierType::DateIdentifier: + if (mFirstIdentifier.isNull() || mIdentifier.toDate() > mFirstIdentifier.toDate()) { + return mIdentifier.toDate().addDays(-1); + } else { + return false; + } + case IdentifierType::NumberIdentifier: + if ((mFirstIdentifier.isNull() && mIdentifier.toInt() > 1) || (!mFirstIdentifier.isNull() && mIdentifier.toInt() > mFirstIdentifier.toInt())) { + return mIdentifier.toInt() - 1; + } else { + return false; + } + case IdentifierType::StringIdentifier: + break; + } + } else if (!mPreviousIdentifier.isNull()) { + // no previousIdentifier if mIdentifier == mFirstIdentifier + switch (identifierType()) { + case IdentifierType::DateIdentifier: + if (!mFirstIdentifier.isNull() && (mIdentifier.toDate() == mFirstIdentifier.toDate())) { + return false; + } + break; + case IdentifierType::NumberIdentifier: + if (!mFirstIdentifier.isNull() && (mIdentifier.toInt() == mFirstIdentifier.toInt())) { + return false; + } + break; + case IdentifierType::StringIdentifier: + break; + } + } + return mPreviousIdentifier; +} + +void ComicProviderWrapper::pageRetrieved(int id, const QByteArray &data) +{ + --mRequests; + if (id == ComicProvider::Image) { + mKrossImage = new ImageWrapper(this, data); + callFunction(QLatin1String("pageRetrieved"), {id, m_engine->newQObject(mKrossImage)}); + if (mRequests < 1) { // Don't finish if we still have pageRequests + finished(); + } + } else { + QStringDecoder codec(mTextCodec); + if (mTextCodec.isEmpty() || !codec.isValid()) { + codec = QStringDecoder(QStringDecoder::encodingForHtml(data).value_or(QStringDecoder::Utf8)); + } + QString html = codec.decode(data); + + callFunction(QLatin1String("pageRetrieved"), {id, html}); + } +} + +void ComicProviderWrapper::pageError(int id, const QString &message) +{ + --mRequests; + callFunction(QLatin1String("pageError"), {id, message}); + if (!functionCalled()) { + Q_EMIT mProvider->error(mProvider); + } +} + +void ComicProviderWrapper::redirected(int id, const QUrl &newUrl) +{ + --mRequests; + callFunction(QLatin1String("redirected"), {id, newUrl.toString()}); + if (mRequests < 1) { // Don't finish while there are still requests + finished(); + } +} + +void ComicProviderWrapper::finished() const +{ + qCDebug(PLASMA_COMIC) << QString::fromLatin1("Author").leftJustified(22, QLatin1Char('.')) << comicAuthor(); + qCDebug(PLASMA_COMIC) << QString::fromLatin1("Website URL").leftJustified(22, QLatin1Char('.')) << mWebsiteUrl; + qCDebug(PLASMA_COMIC) << QString::fromLatin1("Shop URL").leftJustified(22, QLatin1Char('.')) << mShopUrl; + qCDebug(PLASMA_COMIC) << QString::fromLatin1("Title").leftJustified(22, QLatin1Char('.')) << mTitle; + qCDebug(PLASMA_COMIC) << QString::fromLatin1("Additional Text").leftJustified(22, QLatin1Char('.')) << mAdditionalText; + qCDebug(PLASMA_COMIC) << QString::fromLatin1("Identifier").leftJustified(22, QLatin1Char('.')) << mIdentifier; + qCDebug(PLASMA_COMIC) << QString::fromLatin1("First Identifier").leftJustified(22, QLatin1Char('.')) << mFirstIdentifier; + qCDebug(PLASMA_COMIC) << QString::fromLatin1("Last Identifier").leftJustified(22, QLatin1Char('.')) << mLastIdentifier; + qCDebug(PLASMA_COMIC) << QString::fromLatin1("Next Identifier").leftJustified(22, QLatin1Char('.')) << mNextIdentifier; + qCDebug(PLASMA_COMIC) << QString::fromLatin1("Previous Identifier").leftJustified(22, QLatin1Char('.')) << mPreviousIdentifier; + Q_EMIT mProvider->finished(mProvider); +} + +void ComicProviderWrapper::error() const +{ + Q_EMIT mProvider->error(mProvider); +} + +void ComicProviderWrapper::requestPage(const QString &url, int id, const QVariantMap &infos) +{ + QMap map; + + for (auto it = infos.begin(), end = infos.end(); it != end; ++it) { + map[it.key()] = it.value().toString(); + } + mProvider->requestPage(QUrl(url), id, map); + ++mRequests; +} + +void ComicProviderWrapper::requestRedirectedUrl(const QString &url, int id, const QVariantMap &infos) +{ + QMap map; + + for (auto it = infos.begin(), end = infos.end(); it != end; ++it) { + map[it.key()] = it.value().toString(); + } + mProvider->requestRedirectedUrl(QUrl(url), id, map); + ++mRequests; +} + +bool ComicProviderWrapper::functionCalled() const +{ + return mFuncFound; +} + +QVariant ComicProviderWrapper::callFunction(const QString &name, const QJSValueList &args) +{ + if (m_engine) { + mFuncFound = mFunctions.contains(name); + if (mFuncFound) { + auto val = m_engine->globalObject().property(name).call(args); + if (val.isError()) { + qCWarning(PLASMA_COMIC) << "Error when calling function" << name << "with arguments" << QVariant::fromValue(args) << val.toString(); + return QVariant(); + } else { + return val.toVariant(); + } + } + } + return QVariant(); +} + +void ComicProviderWrapper::combine(const QVariant &image, PositionType position) +{ + if (!mKrossImage) { + return; + } + + QImage header; + if (image.typeId() == QMetaType::QString) { + const QString path(mPackage->filePath("images", image.toString())); + if (QFile::exists(path)) { + header = QImage(path); + } else { + return; + } + } else { + ImageWrapper *img = qobject_cast(image.value()); + if (img) { + header = img->image(); + } else { + return; + } + } + const QImage comic = mKrossImage->image(); + int height = 0; + int width = 0; + + switch (position) { + case Top: + case Bottom: + height = header.height() + comic.height(); + width = (header.width() >= comic.width()) ? header.width() : comic.width(); + break; + case Left: + case Right: + height = (header.height() >= comic.height()) ? header.height() : comic.height(); + width = header.width() + comic.width(); + break; + } + + QImage img = QImage(QSize(width, height), QImage::Format_RGB32); + img.fill(header.pixel(QPoint(0, 0))); + + QPainter painter(&img); + + // center and draw the Images + QPoint headerPos; + QPoint comicPos; + + switch (position) { + case Top: + headerPos = QPoint(((width - header.width()) / 2), 0); + comicPos = QPoint(((width - comic.width()) / 2), header.height()); + break; + case Bottom: + headerPos = QPoint(((width - header.width()) / 2), comic.height()); + comicPos = QPoint(((width - comic.width()) / 2), 0); + break; + case Left: + headerPos = QPoint(0, ((height - header.height()) / 2)); + comicPos = QPoint(header.width(), ((height - comic.height()) / 2)); + break; + case Right: + headerPos = QPoint(comic.width(), ((height - header.height()) / 2)); + comicPos = QPoint(0, ((height - comic.height()) / 2)); + break; + } + painter.drawImage(headerPos, header); + painter.drawImage(comicPos, comic); + mKrossImage->setImage(img); +} + +QObject *ComicProviderWrapper::image() +{ + return qobject_cast(mKrossImage); +} diff --git a/applets/comic/engine/comicproviderwrapper.h b/applets/comic/engine/comicproviderwrapper.h new file mode 100644 index 00000000..cb525237 --- /dev/null +++ b/applets/comic/engine/comicproviderwrapper.h @@ -0,0 +1,263 @@ +/* + * SPDX-FileCopyrightText: 2008 Petri Damstén + * SPDX-FileCopyrightText: 2010 Matthias Fuchs + * + * SPDX-License-Identifier: LGPL-2.0-only + */ + +#ifndef COMICPROVIDERWRAPPER_H +#define COMICPROVIDERWRAPPER_H + +#include "comicprovider.h" +#include "types.h" + +#include +#include +#include +#include +#include + +namespace KPackage +{ +class Package; +} +class ComicProviderKross; +class QJSEngine; + +class ImageWrapper : public QObject +{ + Q_OBJECT + Q_PROPERTY(QImage image READ image WRITE setImage) + Q_PROPERTY(QByteArray rawData READ rawData WRITE setRawData) +public: + explicit ImageWrapper(QObject *parent = nullptr, const QByteArray &image = QByteArray()); + + QImage image() const; + /** + * Sets the image, rawData is changed to the new set image + */ + void setImage(const QImage &image); + QByteArray rawData() const; + + /** + * Sets the rawData, image is changed to the new rawData + */ + void setRawData(const QByteArray &rawData); + +public Q_SLOTS: + /** + * Returns the numbers of images contained in the image + * 0 if there is just one image, > 0 if the image format supports animation (the number of frames), + * -1 if there was an error + * @since 4600 + */ + int imageCount() const; + + /** + * Returns an image, if it did not work an null image is returned + * For animations returns the next frame upon each call, if there are no frames left returns a null image + * @see imageCount() + * @since 4600 + */ + QImage read(); + +private: + void resetImageReader(); + +private: + QImage mImage; + mutable QByteArray mRawData; + QBuffer mBuffer; + QImageReader mImageReader; +}; + +class DateWrapper +{ + Q_GADGET + Q_PROPERTY(QDate date READ date WRITE setDate) +public: + explicit DateWrapper(const QDate &date = QDate()); + + QDate date() const; + void setDate(const QDate &date); + static QDate fromVariant(const QVariant &variant); + + Q_INVOKABLE QString toString(const QString &format) const; + Q_INVOKABLE QString toString(int format = 0) const; + +public Q_SLOTS: + DateWrapper addDays(int ndays); + DateWrapper addMonths(int nmonths); + DateWrapper addYears(int nyears); + int day() const; + int dayOfWeek() const; + int dayOfYear() const; + int daysInMonth() const; + int daysInYear() const; + int daysTo(const QVariant d) const; + bool isNull() const; + bool isValid() const; + int month() const; + bool setDate(int year, int month, int day); + int toJulianDay() const; + int weekNumber() const; + int year() const; + +private: + QDate mDate; +}; + +class StaticDateWrapper : public QObject +{ + Q_OBJECT +public: + explicit StaticDateWrapper(QObject *parent = nullptr); + +public Q_SLOTS: + DateWrapper currentDate(); + DateWrapper fromJulianDay(int jd); + DateWrapper fromString(const QString &string, int format = Qt::TextDate); + DateWrapper fromString(const QString &string, const QString &format); + bool isLeapYear(int year); + bool isValid(int year, int month, int day); + QString longDayName(int weekday); + QString longMonthName(int month); + QString shortDayName(int weekday); + QString shortMonthName(int month); +}; + +class ComicProviderWrapper : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool identifierSpecified READ identifierSpecified) + Q_PROPERTY(QString textCodec READ textCodec WRITE setTextCodec) + Q_PROPERTY(QString comicAuthor READ comicAuthor WRITE setComicAuthor) + Q_PROPERTY(QString websiteUrl READ websiteUrl WRITE setWebsiteUrl) + Q_PROPERTY(QString shopUrl READ shopUrl WRITE setShopUrl) + Q_PROPERTY(QString title READ title WRITE setTitle) + Q_PROPERTY(QString additionalText READ additionalText WRITE setAdditionalText) + Q_PROPERTY(QJSValue identifier READ identifier WRITE setIdentifier) + Q_PROPERTY(QJSValue nextIdentifier READ nextIdentifier WRITE setNextIdentifier) + Q_PROPERTY(QJSValue previousIdentifier READ previousIdentifier WRITE setPreviousIdentifier) + Q_PROPERTY(QJSValue firstIdentifier READ firstIdentifier WRITE setFirstIdentifier) + Q_PROPERTY(QJSValue lastIdentifier READ lastIdentifier WRITE setLastIdentifier) + Q_PROPERTY(bool isLeftToRight READ isLeftToRight WRITE setLeftToRight) + Q_PROPERTY(bool isTopToBottom READ isTopToBottom WRITE setTopToBottom) + Q_PROPERTY(int apiVersion READ apiVersion) +public: + enum PositionType { + Left = 0, + Top, + Right, + Bottom, + }; + Q_ENUM(PositionType) + + enum RedirectedUrlType { + PreviousUrl = 0, + CurrentUrl = 1, + NextUrl = 2, + FirstUrl = 3, + LastUrl = 4, + UserUrl = 10, + }; + Q_ENUM(RedirectedUrlType) + + explicit ComicProviderWrapper(ComicProviderKross *parent); + ~ComicProviderWrapper() override; + + int apiVersion() const + { + return 4600; + } + + Q_INVOKABLE void print(const QJSValue &str) + { + qWarning() << str.toString(); + } + + IdentifierType identifierType() const; + QImage comicImage(); + void pageRetrieved(int id, const QByteArray &data); + void pageError(int id, const QString &message); + void redirected(int id, const QUrl &newUrl); + + bool identifierSpecified() const; + QString textCodec() const; + void setTextCodec(const QString &textCodec); + QString comicAuthor() const; + void setComicAuthor(const QString &author); + QString websiteUrl() const; + void setWebsiteUrl(const QString &websiteUrl); + QString shopUrl() const; + void setShopUrl(const QString &shopUrl); + QString title() const; + void setTitle(const QString &title); + QString additionalText() const; + void setAdditionalText(const QString &additionalText); + QJSValue identifier(); + void setIdentifier(const QJSValue &identifier); + QJSValue nextIdentifier(); + void setNextIdentifier(const QJSValue &nextIdentifier); + QJSValue previousIdentifier(); + void setPreviousIdentifier(const QJSValue &previousIdentifier); + QJSValue firstIdentifier(); + void setFirstIdentifier(const QJSValue &firstIdentifier); + QJSValue lastIdentifier(); + void setLastIdentifier(const QJSValue &lastIdentifier); + bool isLeftToRight() const; + void setLeftToRight(bool ltr); + bool isTopToBottom() const; + void setTopToBottom(bool ttb); + + QVariant identifierVariant() const; + QVariant firstIdentifierVariant() const; + QVariant lastIdentifierVariant() const; + QVariant nextIdentifierVariant() const; + QVariant previousIdentifierVariant() const; + +public Q_SLOTS: + void finished() const; + void error() const; + + void requestPage(const QString &url, int id, const QVariantMap &infos = QVariantMap()); + void requestRedirectedUrl(const QString &url, int id, const QVariantMap &infos = QVariantMap()); + void combine(const QVariant &image, PositionType position = Top); + QObject *image(); + + void init(); + +protected: + QVariant callFunction(const QString &name, const QJSValueList &args = {}); + bool functionCalled() const; + QJSValue identifierToScript(const QVariant &identifier); + QVariant identifierFromScript(const QJSValue &identifier) const; + void setIdentifierToDefault(); + void checkIdentifier(QVariant *identifier); + +private: + QJSEngine *m_engine = nullptr; + ComicProviderKross *mProvider; + QStringList mFunctions; + bool mFuncFound; + ImageWrapper *mKrossImage; + static QStringList mExtensions; + KPackage::Package *mPackage; + + QByteArray mTextCodec; + QString mWebsiteUrl; + QString mShopUrl; + QString mTitle; + QString mAdditionalText; + QVariant mIdentifier; + QVariant mNextIdentifier; + QVariant mPreviousIdentifier; + QVariant mFirstIdentifier; + QVariant mLastIdentifier; + int mRequests; + bool mIdentifierSpecified; + bool mIsLeftToRight; + bool mIsTopToBottom; +}; + +#endif diff --git a/applets/comic/engine/plasma-packagestructure-comic.json b/applets/comic/engine/plasma-packagestructure-comic.json new file mode 100644 index 00000000..417f4989 --- /dev/null +++ b/applets/comic/engine/plasma-packagestructure-comic.json @@ -0,0 +1,5 @@ +{ + "KPackageStructure": "Plasma/Comic", + "X-Plasma-PackageFileFilter": "*.comic", + "X-Plasma-PackageFileMimetypes": "application/zip" +} diff --git a/applets/comic/engine/types.h b/applets/comic/engine/types.h new file mode 100644 index 00000000..4682415d --- /dev/null +++ b/applets/comic/engine/types.h @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2022 Alexander Lohnau + * SPDX-License-Identifier: LGPL-2.0-or-later + */ +#ifndef COMIC_ENGINE_TYPES +#define COMIC_ENGINE_TYPES + +#include +#include +#include + +/** + * Describes the type of how this comic provider + * references the previous or next comic strip. + */ +enum class IdentifierType { + DateIdentifier = 0, ///< References by date + NumberIdentifier, ///< References by numerical identifier + StringIdentifier, ///< References by arbitrary string +}; + +inline IdentifierType stringToIdentifierType(const QString type) +{ + if (type == QLatin1String("Date")) { + return IdentifierType::DateIdentifier; + } else if (type == QLatin1String("Number")) { + return IdentifierType::NumberIdentifier; + } else if (type == QLatin1String("String")) { + return IdentifierType::StringIdentifier; + } + return IdentifierType::StringIdentifier; +} + +struct ComicProviderInfo { + QString pluginId; + QString name; + QString icon; +}; + +struct ComicMetaData { + QString stripTitle; + QUrl imageUrl; + QImage image; + QUrl websiteUrl; + QUrl shopUrl; + QString firstStripIdentifier; + QString previousStripIdentifier; + QString nextIdentifier; + QString previousIdentifier; + QString author; + QString comicAuthor; + QString additionalText; + QString identifier; + IdentifierType identifierType; + bool isLeftToRight = false; + bool isTopToBottom = false; + QString lastCachedStripIdentifier; + QString providerName; + bool error = false; + bool errorAutomaticallyFixable = false; +}; + +#endif diff --git a/applets/comic/package/contents/config/config.qml b/applets/comic/package/contents/config/config.qml new file mode 100644 index 00000000..bc7f76ac --- /dev/null +++ b/applets/comic/package/contents/config/config.qml @@ -0,0 +1,27 @@ +/* + * SPDX-FileCopyrightText: 2013 Bhushan Shah + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.1 + +import org.kde.plasma.configuration 2.0 + +ConfigModel { + ConfigCategory { + name: i18nc("@title", "General") + icon: "face-smile-big" + source: "configGeneral.qml" + } + ConfigCategory { + name: i18nc("@title", "Appearance") + icon: "preferences-desktop-color" + source: "configAppearance.qml" + } + ConfigCategory { + name: i18nc("@title", "Advanced") + icon: "system-run" + source: "configAdvanced.qml" + } +} diff --git a/applets/comic/package/contents/ui/ButtonBar.qml b/applets/comic/package/contents/ui/ButtonBar.qml new file mode 100644 index 00000000..53e0ad58 --- /dev/null +++ b/applets/comic/package/contents/ui/ButtonBar.qml @@ -0,0 +1,65 @@ +/* + * SPDX-FileCopyrightText: 2012 Reza Fatahilah Shah + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.1 +import org.kde.plasma.core as PlasmaCore +import org.kde.ksvg 1.0 as KSvg +import org.kde.plasma.components 3.0 as PlasmaComponents3 + +Item { + id: root + + implicitWidth: rowButton.width + background.margins.left + background.margins.right + implicitHeight: rowButton.height + background.margins.top + background.margins.bottom + + signal prevClicked + signal nextClicked + signal zoomClicked + + KSvg.FrameSvgItem { + id: background + + anchors.fill: parent + + imagePath: "widgets/toolbar" + prefix: "raised" + } + + Row { + id: rowButton + + x: background.margins.left + y: background.margins.top + + spacing: 4 + //ToolButton or Button in C++ use PushButton? + PlasmaComponents3.Button { + id: prevButton + + icon.name: "arrow-left" + enabled: (comicData.prev != undefined && comicData.prev.length > 0) + } + + PlasmaComponents3.Button { + id: zoomButton + + icon.name: "zoom-original" + } + + PlasmaComponents3.Button { + id: nextButton + + icon.name: "arrow-right" + enabled: (comicData.next != undefined && comicData.next.length > 0) + } + } + + Component.onCompleted: { + prevButton.clicked.connect(root.prevClicked); + nextButton.clicked.connect(root.nextClicked); + zoomButton.clicked.connect(root.zoomClicked); + } +} diff --git a/applets/comic/package/contents/ui/ComicBottomInfo.qml b/applets/comic/package/contents/ui/ComicBottomInfo.qml new file mode 100644 index 00000000..3438f807 --- /dev/null +++ b/applets/comic/package/contents/ui/ComicBottomInfo.qml @@ -0,0 +1,108 @@ +/* + * SPDX-FileCopyrightText: 2012 Reza Fatahilah Shah + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.1 +import org.kde.plasma.core as PlasmaCore +import org.kde.kirigami 2.20 as Kirigami +import org.kde.plasma.components 3.0 as PlasmaComponents3 +import org.kde.kquickcontrolsaddons 2.0 +import org.kde.plasma.plasmoid + +Item { + id: root + + implicitWidth: 10 + implicitHeight: comicIdentifier.height + + property bool showUrl: false + property bool showIdentifier: false + property variant comicData + + visible: (comicIdentifier.text.length > 0 || comicUrl.text.length > 0) + + PlasmaComponents3.Label { + id: comicIdentifier + + anchors { + left: root.left + top: root.top + bottom: root.bottom + right: comicUrl.left + leftMargin: 2 + } + + color: Kirigami.Theme.textColor + visible: (showIdentifier && comicIdentifier.text.length > 0) + text: (showIdentifier && comicData.currentReadable != undefined) ? comicData.currentReadable : "" + textFormat: Text.PlainText + + MouseArea { + id: idLabelArea + + anchors.fill: parent + + hoverEnabled: true + + onEntered: { + parent.color = Kirigami.Theme.highlightColor; + } + + onExited: { + parent.color = Kirigami.Theme.textColor; + } + + onClicked: { + Plasmoid.goJump(); + } + + PlasmaCore.ToolTipArea { + anchors.fill: idLabelArea + mainText: i18nc("@info:tooltip", "Jump to strip…") + } + } + } + + PlasmaComponents3.Label { + id:comicUrl + + anchors { + top: root.top + bottom: root.bottom + right: root.right + rightMargin: 2 + } + + color: Kirigami.Theme.textColor + visible: (showUrl && comicUrl.text.length > 0) + text: (showUrl && comicData.websiteHost.length > 0) ? comicData.websiteHost : "" + textFormat: Text.PlainText + + MouseArea { + id: idUrlLabelArea + + anchors.fill: parent + + hoverEnabled: true + + onEntered: { + parent.color = Kirigami.Theme.highlightColor; + } + + onExited: { + parent.color = Kirigami.Theme.textColor; + } + + onClicked: { + Plasmoid.shop(); + } + + PlasmaCore.ToolTipArea { + anchors.fill: idUrlLabelArea + mainText: i18nc("@info:tooltip", "Visit the comic website") + } + } + } +} diff --git a/applets/comic/package/contents/ui/ComicCentralView.qml b/applets/comic/package/contents/ui/ComicCentralView.qml new file mode 100644 index 00000000..05362d58 --- /dev/null +++ b/applets/comic/package/contents/ui/ComicCentralView.qml @@ -0,0 +1,135 @@ +/* + * SPDX-FileCopyrightText: 2012 Reza Fatahilah Shah + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.1 +import org.kde.plasma.core as PlasmaCore +import org.kde.kirigami 2.20 as Kirigami +import org.kde.plasma.components 3.0 as PlasmaComponents3 +import org.kde.kquickcontrolsaddons 2.0 +import org.kde.plasma.plasmoid + +Item { + id: root + + width: Kirigami.Units.gridUnit + height: Kirigami.Units.gridUnit + + property variant comicData + + PlasmaComponents3.ToolButton { + id: arrowLeft + + anchors { + left: root.left + verticalCenter: root.verticalCenter + } + + icon.name: "go-previous" + visible: (!Plasmoid.arrowsOnHover && (comicData.prev !== undefined)) + + onClicked: { + Plasmoid.updateComic(comicData.prev); + } + } + PlasmaCore.ToolTipArea { + id: tooltip + anchors { + left: arrowLeft.visible ? arrowLeft.right : root.left + right: arrowRight.visible ? arrowRight.left : root.right + leftMargin: arrowLeft.visible ? 4 : 0 + rightMargin: arrowRight.visible ? 4 : 0 + top: root.top + bottom: root.bottom + } + subText: Plasmoid.comicData.additionalText + active: subText + + MouseArea { + id: comicImageArea + + anchors.fill: parent + + hoverEnabled: true + preventStealing: false + acceptedButtons: Qt.LeftButton | Qt.MiddleButton + + onClicked: { + if (mouse.button == Qt.MiddleButton && Plasmoid.middleClick) { + fullDialog.toggleVisibility(); + } + } + + ImageWidget { + id: comicImage + + anchors.fill: parent + enabled: false + + image: Plasmoid.comicData.image + actualSize: Plasmoid.showActualSize + isLeftToRight: Plasmoid.comicData.isLeftToRight + isTopToBottom: Plasmoid.comicData.isTopToBottom + } + + ButtonBar { + id: buttonBar + + anchors { + horizontalCenter: parent.horizontalCenter + bottom: parent.bottom + bottomMargin: 10 + } + + visible: Plasmoid.arrowsOnHover && comicImageArea.containsMouse + opacity: 0 + + onPrevClicked: { + Plasmoid.updateComic(comicData.prev); + } + + onNextClicked: { + Plasmoid.updateComic(comicData.next); + } + + onZoomClicked: { + fullDialog.toggleVisibility(); + } + + states: State { + name: "show"; when: (Plasmoid.arrowsOnHover && comicImageArea.containsMouse) + PropertyChanges { target: buttonBar; opacity: 1; } + } + + transitions: Transition { + from: ""; to: "show"; reversible: true + NumberAnimation { properties: "opacity"; duration: Kirigami.Units.longDuration; easing.type: Easing.InOutQuad } + } + } + } + } + + PlasmaComponents3.ToolButton { + id: arrowRight + + anchors { + right: root.right + verticalCenter: root.verticalCenter + } + + icon.name: "go-next" + visible: (!Plasmoid.arrowsOnHover && (comicData.next !== undefined)) + + onClicked: { + Plasmoid.updateComic(comicData.next); + } + } + + FullViewWidget { + id: fullDialog + + image: Plasmoid.comicData.image + } +} diff --git a/applets/comic/package/contents/ui/FullViewWidget.qml b/applets/comic/package/contents/ui/FullViewWidget.qml new file mode 100644 index 00000000..c6f76ea5 --- /dev/null +++ b/applets/comic/package/contents/ui/FullViewWidget.qml @@ -0,0 +1,79 @@ +/* + * SPDX-FileCopyrightText: 2012 Reza Fatahilah Shah + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.1 +import org.kde.plasma.core as PlasmaCore +import org.kde.plasma.components 3.0 as PlasmaComponents +import org.kde.kquickcontrolsaddons 2.0 +import QtQuick.Layouts 1.1 +import org.kde.plasma.plasmoid +import org.kde.kirigami as Kirigami + +PlasmaCore.Dialog { + id: root + + property alias image: comicPicture.image + + flags: Qt.Popup + visible: false + hideOnWindowDeactivate: true + + function toggleVisibility() + { + root.visible = !root.visible; + if (root.visible) { + Plasmoid.positionFullView(root); + root.activateWindow; + } + } + + function close() { + root.visible = false; + } + + mainItem: PlasmaComponents.ScrollView { + id: mainScrollArea + + anchors.fill: parent + + // 4×gridUnit in case image is empty, to show something and avoid anchor loops. + // value is arbitrary, but small enough to be unlikely to increase popup size for real comics + Layout.minimumWidth: Math.max(Kirigami.Units.gridUnit * 4, comicPicture.nativeWidth) + Layout.maximumWidth: Layout.minimumWidth + Layout.minimumHeight: Math.max(Kirigami.Units.gridUnit * 4, comicPicture.nativeHeight) + Layout.maximumHeight: Layout.minimumHeight + + Flickable { + id: viewContainer + + anchors.fill: parent + + contentWidth: comicPicture.nativeWidth + contentHeight: comicPicture.nativeHeight + + //clip: true + + QImageItem { + id: comicPicture + + anchors.fill: parent + + smooth: true + fillMode: QImageItem.PreserveAspectFit + + MouseArea { + id: dialogMouseArea + + anchors.fill: comicPicture + + onClicked: { + root.close(); + } + } + } + } + } +} diff --git a/applets/comic/package/contents/ui/ImageWidget.qml b/applets/comic/package/contents/ui/ImageWidget.qml new file mode 100644 index 00000000..2ae1cc34 --- /dev/null +++ b/applets/comic/package/contents/ui/ImageWidget.qml @@ -0,0 +1,65 @@ +/* + * SPDX-FileCopyrightText: 2012 Reza Fatahilah Shah + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.1 +import org.kde.plasma.components 3.0 as PlasmaComponents +import org.kde.kquickcontrolsaddons 2.0 + +PlasmaComponents.ScrollView { + id: root + + width: comicPicture.nativeWidth + height: comicPicture.nativeHeight + + property bool actualSize: false + property bool isLeftToRight: true + property bool isTopToBottom: true + + property alias image: comicPicture.image + + function calculateContentWidth() { + return actualSize ? (comicPicture.nativeWidth > viewContainer.width ? comicPicture.nativeWidth : viewContainer.width) : viewContainer.width; + } + + function calculateContentHeight() { + return actualSize ? (comicPicture.nativeHeight > viewContainer.height ? comicPicture.nativeHeight : viewContainer.height) : viewContainer.height; + } + + Flickable { + id: viewContainer + + anchors.fill:parent + + contentWidth: comicPictureHolder.width + contentHeight: comicPictureHolder.height + + clip: true + + Item { + id: comicPictureHolder + + width: Math.max(comicPicture.width, viewContainer.width); + height: Math.max(comicPicture.height, viewContainer.height); + + QImageItem { + id: comicPicture + + anchors.centerIn: parent + + width: actualSize ? comicPicture.nativeWidth : viewContainer.width + height: actualSize ? comicPicture.nativeHeight : viewContainer.height + + onImageChanged: { + viewContainer.contentX = (root.isLeftToRight) ? 0 : ( viewContainer.contentWidth - viewContainer.width); + viewContainer.contentY = (root.isTopToBottom) ? 0 : ( viewContainer.contentHeight - viewContainer.height); + } + + smooth: true + fillMode: QImageItem.PreserveAspectFit + } + } + } +} diff --git a/applets/comic/package/contents/ui/configAdvanced.qml b/applets/comic/package/contents/ui/configAdvanced.qml new file mode 100644 index 00000000..1da92c4d --- /dev/null +++ b/applets/comic/package/contents/ui/configAdvanced.qml @@ -0,0 +1,55 @@ +/* + * SPDX-FileCopyrightText: 2015 Marco Martin + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.0 +import QtQuick.Controls 2.5 as Controls +import QtQuick.Layouts 1.1 as Layouts + +import org.kde.kirigami 2.20 as Kirigami +import org.kde.plasma.plasmoid +import org.kde.kcmutils as KCM + +KCM.SimpleKCM { + id: root + + signal configurationChanged + + function saveConfig() { + Plasmoid.showErrorPicture = showErrorPicture.checked; + Plasmoid.maxComicLimit = maxComicLimit.value; + + Plasmoid.saveConfig(); + Plasmoid.configChanged(); + } + + Kirigami.FormLayout { + Component.onCompleted: { + showErrorPicture.checked = Plasmoid.showErrorPicture; + maxComicLimit.value = Plasmoid.maxComicLimit; + } + + Layouts.RowLayout { + Kirigami.FormData.label: i18nc("@label:spinbox", "Comic cache:") + + Controls.SpinBox { + id: maxComicLimit + stepSize: 1 + onValueChanged: root.configurationChanged(); + } + + Controls.Label { + text: i18ncp("@item:valuesuffix spacing to number + unit", "strip per comic", "strips per comic") + textFormat: Text.PlainText + } + } + + Controls.CheckBox { + id: showErrorPicture + text: i18nc("@option:check", "Display error when downloading comic fails") + onCheckedChanged: root.configurationChanged(); + } + } +} diff --git a/applets/comic/package/contents/ui/configAppearance.qml b/applets/comic/package/contents/ui/configAppearance.qml new file mode 100644 index 00000000..6e335f0f --- /dev/null +++ b/applets/comic/package/contents/ui/configAppearance.qml @@ -0,0 +1,83 @@ +/* + * SPDX-FileCopyrightText: 2015 Marco Martin + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.1 +import QtQuick.Controls 2.5 as Controls +import QtQuick.Layouts 1.1 as Layouts + +import org.kde.kirigami 2.20 as Kirigami +import org.kde.plasma.plasmoid +import org.kde.kcmutils as KCM + +KCM.SimpleKCM { + id: root + + signal configurationChanged + + function saveConfig() { + Plasmoid.arrowsOnHover = showArrowsOnOver.checked; + Plasmoid.showComicTitle = showComicTitle.checked; + Plasmoid.showComicIdentifier = showIdentifier.checked; + Plasmoid.showComicAuthor = showAuthor.checked; + Plasmoid.showComicUrl = showUrl.checked; + + Plasmoid.saveConfig(); + Plasmoid.configChanged(); + } + + Kirigami.FormLayout { + Component.onCompleted: { + showArrowsOnOver.checked = Plasmoid.arrowsOnHover; + showComicTitle.checked = Plasmoid.showComicTitle; + showIdentifier.checked = Plasmoid.showComicIdentifier; + showAuthor.checked = Plasmoid.showComicAuthor; + showUrl.checked = Plasmoid.showComicUrl; + } + + Controls.CheckBox { + id: showComicTitle + Kirigami.FormData.label: i18nc ("Heading for showing various elements of a comic", "Show:") + text: i18nc("@option:check", "Comic title") + onCheckedChanged: root.configurationChanged(); + } + + Controls.CheckBox { + id: showIdentifier + text: i18nc("@option:check", "Comic identifier") + onCheckedChanged: root.configurationChanged(); + } + + Controls.CheckBox { + id: showAuthor + text: i18nc("@option:check", "Comic author") + onCheckedChanged: root.configurationChanged(); + } + + Controls.CheckBox { + id: showUrl + text: i18nc("@option:check", "Comic URL") + onCheckedChanged: root.configurationChanged(); + } + + Item { + Kirigami.FormData.isSection: true + } + + Controls.RadioButton { + id: alwaysShowArrows + Kirigami.FormData.label: i18n ("Show navigation buttons:") + text: i18nc("@option:check", "Always") + checked: !showArrowsOnOver.checked + onCheckedChanged: root.configurationChanged(); + } + + Controls.RadioButton { + id: showArrowsOnOver + text: i18nc("@option:check", "Only on hover") + onCheckedChanged: root.configurationChanged(); + } + } +} diff --git a/applets/comic/package/contents/ui/configGeneral.qml b/applets/comic/package/contents/ui/configGeneral.qml new file mode 100644 index 00000000..9a207418 --- /dev/null +++ b/applets/comic/package/contents/ui/configGeneral.qml @@ -0,0 +1,121 @@ +/* + * SPDX-FileCopyrightText: 2015 Marco Martin + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.0 +import QtQuick.Controls 2.5 as Controls +import QtQuick.Layouts 1.1 as Layouts + +import org.kde.newstuff 1.62 as NewStuff +import org.kde.kirigami 2.20 as Kirigami +import org.kde.plasma.plasmoid +import org.kde.kcmutils as KCM + +KCM.SimpleKCM { + id: root + + signal configurationChanged + + function saveConfig() { + var newTabs = []; + for (var i in providerColumn.children) { + if (providerColumn.children[i].checked) { + newTabs.push(providerColumn.children[i].plugin) + } + } + Plasmoid.tabIdentifiers = newTabs; + + Plasmoid.middleClick = middleClickCheckBox.checked; + + Plasmoid.checkNewComicStripsInterval = checkNewComicStripsInterval.value; + Plasmoid.providerUpdateInterval = providerUpdateInterval.value; + + Plasmoid.saveConfig(); + Plasmoid.configChanged(); + } + + Kirigami.FormLayout { + Component.onCompleted: { + middleClickCheckBox.checked = Plasmoid.middleClick; + checkNewComicStripsInterval.value = Plasmoid.checkNewComicStripsInterval; + providerUpdateInterval.value = Plasmoid.providerUpdateInterval + } + + Item { + Kirigami.FormData.isSection: true + } + + Layouts.ColumnLayout { + id: providerColumn + Kirigami.FormData.label: i18nc("@title:group", "Comics:") + Kirigami.FormData.buddyFor: children[1] // 0 is the Repeater + + Repeater { + model: Plasmoid.availableComicsModel + delegate: Controls.CheckBox { + id: checkbox + text: model.display + checked: model.checked + property string plugin: model.plugin + Component.onCompleted: { + checkbox.checked = Plasmoid.tabIdentifiers.indexOf(model.plugin) !== -1 + } + onCheckedChanged: root.configurationChanged(); + } + } + } + + NewStuff.Button { + id: ghnsButton + text: i18nc("@action:button", "Get New Comics…") + configFile: "comic.knsrc" + onEntryEvent: function(entry, event) { + if (event == 1) { + Plasmoid.loadProviders() + } + } + } + + Controls.CheckBox { + id: middleClickCheckBox + text: i18nc("@option:check", "Middle-click on comic to display at original size") + onCheckedChanged: root.configurationChanged(); + } + + Item { + Kirigami.FormData.isSection: true + } + + Layouts.RowLayout { + Kirigami.FormData.label: i18nc("@label:spinbox", "Check for new plugins every:") + + Controls.SpinBox { + id: providerUpdateInterval + stepSize: 1 + onValueChanged: root.configurationChanged(); + } + + Controls.Label { + text: i18ncp("@item:valuesuffix spacing to number + unit", "day", "days") + textFormat: Text.PlainText + } + } + + Layouts.RowLayout { + Kirigami.FormData.label: i18nc("@label:spinbox", "Check for new comics every:") + + Controls.SpinBox { + id: checkNewComicStripsInterval + stepSize: 1 + onValueChanged: root.configurationChanged(); + } + + Controls.Label { + text: i18ncp("@item:valuesuffix spacing to number + unit (minutes)", "minute", "minutes") + textFormat: Text.PlainText + } + } + } +} diff --git a/applets/comic/package/contents/ui/main.qml b/applets/comic/package/contents/ui/main.qml new file mode 100644 index 00000000..8ecf3daa --- /dev/null +++ b/applets/comic/package/contents/ui/main.qml @@ -0,0 +1,214 @@ +/* + * SPDX-FileCopyrightText: 2012 Reza Fatahilah Shah + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.1 +import QtQuick.Layouts 1.1 + +import org.kde.plasma.plasmoid 2.0 +import org.kde.plasma.core as PlasmaCore +import org.kde.kirigami 2.20 as Kirigami +import org.kde.plasma.components 3.0 as PlasmaComponents3 +import org.kde.kquickcontrolsaddons 2.0 + +PlasmoidItem { + id: mainWindow + + readonly property int implicitWidth: Kirigami.Units.gridUnit * 40 + readonly property int implicitHeight: Kirigami.Units.gridUnit * 15 + Plasmoid.backgroundHints: PlasmaCore.Types.DefaultBackground | PlasmaCore.Types.ConfigurableBackground + switchWidth: { + if (centerLayout.comicData.image) { + return Math.max(minimumWidth, Math.min(centerLayout.comicData.image.nativeWidth * 0.6, implicitWidth)); + } else { + return Kirigami.Units.gridUnit * 8; + } + } + switchHeight: { + if (centerLayout.comicData.image) { + return Math.max(minimumHeight, Math.min(centerLayout.comicData.image.nativeHeight * 0.6, implicitHeight)); + } else { + return Kirigami.Units.gridUnit * 8; + } + } + Plasmoid.icon: "face-laughing" + + width: implicitWidth + height: implicitHeight + + readonly property int minimumWidth: Kirigami.Units.gridUnit * 8 + readonly property int minimumHeight: Kirigami.Units.gridUnit * 8 + readonly property bool showComicAuthor: plasmoid.showComicAuthor + readonly property bool showComicTitle: plasmoid.showComicTitle + readonly property bool showErrorPicture: plasmoid.showErrorPicture + readonly property bool middleClick: plasmoid.middleClick + + Connections { + target: plasmoid + + function onComicsModelChanged() { + comicTabbar.currentTab = comicTabbar.layout.children[1]; + } + + function onTabHighlightRequest(id, highlight) { + for (var i = 0; i < comicTabbar.layout.children.length; ++i) { + var button = comicTabbar.layout.children[i]; + + if (button.key !== undefined && button.key == id) { + button.highlighted = highlight; + } + } + } + + function onShowNextNewStrip() { + var firstButton = undefined; + + for (var i = 0; i < comicTabbar.layout.children.length; ++i) { + var button = comicTabbar.layout.children[i]; + if (button.key !== undefined && button.highlighted == true) { + //key is ordered + if (button.key > comicTabbar.currentTab.key) { + comicTabbar.currentTab = button; + return; + } else if (firstButton === undefined){ + firstButton = button; + } + } + } + + if (firstButton !== undefined) { + comicTabbar.currentTab = firstButton; + } + } + } + + Item { + anchors.fill: parent + PlasmaComponents3.TabBar { + id: comicTabbar + + anchors { + left: parent.left + right: parent.right + } + + visible: plasmoid.tabIdentifiers.length > 1 + + onCurrentIndexChanged: { + console.log("onCurrentTabChanged:" + comicTabbar.currentItem.key); + plasmoid.tabChanged(comicTabbar.currentItem.key); + } + + Repeater { + model: plasmoid.comicsModel + delegate: PlasmaComponents3.TabButton { + id: tabButton + + readonly property string key: model.key + property bool highlighted: model.highlight + + text: model.title + icon.source: model.icon + } + } + } + + PlasmaComponents3.Label { + id: topInfo + + anchors { + top: comicTabbar.visible ? comicTabbar.bottom : parent.top + left: parent.left + right: parent.right + } + + visible: (topInfo.text.length > 0) + horizontalAlignment: Text.AlignHCenter + text: (showComicAuthor || showComicTitle) ? getTopInfo() : "" + textFormat: Text.PlainText + + function getTopInfo() { + var tempTop = ""; + + if ( showComicTitle ) { + tempTop = plasmoid.comicData.title; + tempTop += ( ( (plasmoid.comicData.stripTitle.length > 0) && (plasmoid.comicData.title.length > 0) ) ? " - " : "" ) + plasmoid.comicData.stripTitle; + } + + if ( showComicAuthor && + (plasmoid.comicData.author != undefined || plasmoid.comicData.author.length > 0) ) { + tempTop = ( tempTop.length > 0 ? plasmoid.comicData.author + ": " + tempTop : plasmoid.comicData.author ); + } + + return tempTop; + } + } + + ComicCentralView { + id: centerLayout + + anchors { + left: parent.left + right: parent.right + bottom: (bottomInfo.visible) ? bottomInfo.top : parent.bottom + top: (topInfo.visible) ? topInfo.bottom : (comicTabbar.visible ? comicTabbar.bottom : parent.top) + topMargin: (comicTabbar.visible) ? 3 : 0 + } + + visible: plasmoid.tabIdentifiers.length > 0 + comicData: plasmoid.comicData + } + + ComicBottomInfo { + id:bottomInfo + + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + + comicData: plasmoid.comicData + showUrl: plasmoid.showComicUrl + showIdentifier: plasmoid.showComicIdentifier + } + } + + states: [ + State { + name: "topInfoVisible" + when: topInfo.visible && !bottomInfo.visible + AnchorChanges { + target: centerLayout + anchors.top: topInfo.bottom + } + }, + State { + name: "bottomInfoVisible" + when: bottomInfo.visible && !topInfo.visible + AnchorChanges { + target: centerLayout + anchors.bottom: bottomInfo.top + } + }, + State { + name: "topBottomInfoVisible" + when: bottomInfo.visible && topInfo.visible + AnchorChanges { + target: centerLayout + anchors.top: topInfo.bottom + anchors.bottom: bottomInfo.top + } + } + ] + + transitions: + Transition { + AnchorAnimation { + duration: Kirigami.Units.veryLongDuration + easing.type: Easing.InOutQuad + } + } +} diff --git a/applets/comic/package/metadata.json b/applets/comic/package/metadata.json new file mode 100644 index 00000000..3c4fe271 --- /dev/null +++ b/applets/comic/package/metadata.json @@ -0,0 +1,146 @@ +{ + "KPackageStructure": "Plasma/Applet", + "KPlugin": { + "Authors": [ + { + "Email": "rshah0385@kireihana.com", + "Name": "Reza Fatahilah Shah", + "Name[ar]": "رضا فتح الله شاه", + "Name[az]": "Reza Fatahilah Shah", + "Name[bg]": "Reza Fatahilah Shah", + "Name[ca@valencia]": "Reza Fatahilah Shah", + "Name[ca]": "Reza Fatahilah Shah", + "Name[cs]": "Reza Fatahilah Shah", + "Name[da]": "Reza Fatahilah Shah", + "Name[de]": "Reza Fatahilah Shah", + "Name[en_GB]": "Reza Fatahilah Shah", + "Name[eo]": "Reza Fatahilah Shah", + "Name[es]": "Reza Fatahilah Shah", + "Name[eu]": "Reza Fatahilah Shah", + "Name[fi]": "Reza Fatahilah Shah", + "Name[fr]": "Reza Fatahilah Shah", + "Name[gl]": "Reza Fatahilah Shah", + "Name[he]": "רזא פתחילה שאה", + "Name[hu]": "Reza Fatahilah Shah", + "Name[ia]": "Reza Fatahilah Shah", + "Name[id]": "Reza Fatahilah Shah", + "Name[is]": "Reza Fatahilah Shah", + "Name[it]": "Reza Fatahilah Shah", + "Name[ja]": "Reza Fatahilah Shah", + "Name[ka]": "Reza Fatahilah Shah", + "Name[ko]": "Reza Fatahilah Shah", + "Name[lt]": "Reza Fatahilah Shah", + "Name[lv]": "Reza Fatahilah Shah", + "Name[nl]": "Reza Fatahilah Shah", + "Name[nn]": "Reza Fatahilah Shah", + "Name[pl]": "Reza Fatahilah Shah", + "Name[pt]": "Reza Fatahilah Shah", + "Name[pt_BR]": "Reza Fatahilah Shah", + "Name[ro]": "Reza Fatahilah Shah", + "Name[ru]": "Reza Fatahilah Shah", + "Name[sk]": "Reza Fatahilah Shah", + "Name[sl]": "Reza Fatahilah Shah", + "Name[sv]": "Reza Fatahilah Shah", + "Name[tr]": "Reza Fatahilah Şah", + "Name[uk]": "Reza Fatahilah Shah", + "Name[vi]": "Reza Fatahilah Shah", + "Name[x-test]": "xxReza Fatahilah Shahxx", + "Name[zh_CN]": "Reza Fatahilah Shah", + "Name[zh_TW]": "Reza Fatahilah Shah" + } + ], + "BugReportUrl": "https://bugs.kde.org/enter_bug.cgi?product=kdeplasma-addons&component=Comic", + "Category": "Fun and Games", + "Description": "View comic strips from the Internet", + "Description[ar]": "اعرض شرائط هزلية من الإنترنت", + "Description[az]": "Komikslərə internetdən baxmaq", + "Description[bg]": "Преглед на комикси от интернет", + "Description[ca@valencia]": "Mostra una tira còmica des d'Internet", + "Description[ca]": "Mostra una tira còmica des d'Internet", + "Description[cs]": "Ukáže komiksový proužek z internetu", + "Description[da]": "Se tegneserier fra internettet", + "Description[de]": "Comics aus dem Internet anzeigen", + "Description[en_GB]": "View comic strips from the Internet", + "Description[eo]": "Rigardi bildstriojn el la Interreto", + "Description[es]": "Ver tiras de cómics de Internet", + "Description[eu]": "Interneteko komiki zerrendak ikusi", + "Description[fi]": "Näytä sarjakuvastrippejä internetistä", + "Description[fr]": "Afficher des bandes dessinées à partir d'Internet", + "Description[gl]": "Mostra bandas deseñadas de Internet", + "Description[he]": "הצגת רצועות קומיקס מהאינטרנט", + "Description[hu]": "Képregények megtekintése az internetről", + "Description[ia]": "Vide bandas de comic ex le internet", + "Description[id]": "Lihat komik strip dari internet", + "Description[is]": "Skoða myndasögur af netinu", + "Description[it]": "Vedi una striscia di fumetti da Internet.", + "Description[ja]": "インターネット上のコミックを表示します", + "Description[ka]": "ინტერნეტიდან გადმოწერილი კომიქსების ნახვა", + "Description[ko]": "인터넷 만화 보기", + "Description[lt]": "ŽiÅ«rėti komiksų pieÅ¡inių seriją iÅ¡ interneto", + "Description[lv]": "Skatieties internetā esoÅ¡us komiksus", + "Description[nl]": "Toont stripboeken van het internet", + "Description[nn]": "Vis teikneseriar frÃ¥ Internett", + "Description[pl]": "Przegląda komiksy z Internetu", + "Description[pt]": "Ver as bandas desenhadas da Internet", + "Description[pt_BR]": "Exibe uma tirinha da Internet", + "Description[ro]": "Arată benzi desenate din Internet", + "Description[ru]": "Просмотр комиксов из Интернета", + "Description[sk]": "Zobrazuje komiksové pásy z internetu", + "Description[sl]": "Oglej si komične stripe iz interneta", + "Description[sv]": "Visa tecknade serier frÃ¥n Internet", + "Description[tr]": "İnternetten çizgi roman şeritlerini görüntüle", + "Description[uk]": "Показує стрічку з жартами з Інтернету", + "Description[vi]": "Xem các truyện tranh trên Liên Mạng", + "Description[x-test]": "xxView comic strips from the Internetxx", + "Description[zh_CN]": "阅读来自互联网的连环画", + "Description[zh_TW]": "看網路上的連環漫畫", + "Icon": "face-smile-big", + "Id": "org.kde.plasma.comic", + "License": "GPL-2.0+", + "Name": "Comic Strip", + "Name[ar]": "شريط هزلي", + "Name[az]": "Komikslər", + "Name[bg]": "Лента с комикси", + "Name[ca@valencia]": "Tira còmica", + "Name[ca]": "Tira còmica", + "Name[cs]": "Komiksový proužek", + "Name[da]": "Tegneserie", + "Name[de]": "Comic", + "Name[en_GB]": "Comic Strip", + "Name[eo]": "Bildostrio", + "Name[es]": "Tira de cómic", + "Name[eu]": "Komiki zerrenda", + "Name[fi]": "Sarjakuvastrippi", + "Name[fr]": "Bandes dessinées", + "Name[gl]": "Banda deseñada", + "Name[he]": "רצועת קומיקס", + "Name[hu]": "Képregénycsík", + "Name[ia]": "Banda de comic", + "Name[id]": "Komik Strip", + "Name[is]": "Myndasögur", + "Name[it]": "Striscia di fumetti", + "Name[ja]": "コミック・ストリップ", + "Name[ka]": "კომიქსები", + "Name[ko]": "만화 조각", + "Name[lt]": "Komiksų pieÅ¡inių serija", + "Name[lv]": "Komikss", + "Name[nl]": "Stripboek", + "Name[nn]": "Teikne­serie", + "Name[pl]": "Komiksy", + "Name[pt]": "Banda Desenhada", + "Name[pt_BR]": "Tirinha", + "Name[ro]": "Benzi desenate", + "Name[ru]": "Комиксы", + "Name[sk]": "Komiksový pás", + "Name[sl]": "Comic Strip", + "Name[sv]": "Tecknad serie", + "Name[tr]": "Çizgi Roman Şeridi", + "Name[uk]": "Комічна стрічка", + "Name[vi]": "Truyện tranh", + "Name[x-test]": "xxComic Stripxx", + "Name[zh_CN]": "连环画", + "Name[zh_TW]": "連環漫畫", + "Website": "https://kde.org/plasma-desktop/" + }, + "X-Plasma-API-Minimum-Version": "6.0" +} diff --git a/applets/comic/stripselector.cpp b/applets/comic/stripselector.cpp new file mode 100644 index 00000000..5ef7b0ea --- /dev/null +++ b/applets/comic/stripselector.cpp @@ -0,0 +1,167 @@ +/* + * SPDX-FileCopyrightText: 2012 Matthias Fuchs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "stripselector.h" +#include "comicdata.h" +#include "stripselector_p.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +// NOTE based on GotoPageDialog KDE/kdegraphics/okular/part.cpp +// BEGIN choose a strip dialog +class ChooseStripNumDialog : public QDialog +{ +public: + ChooseStripNumDialog(QWidget *parent, int current, int min, int max) + : QDialog(parent) + { + setWindowTitle(i18nc("@title:window", "Go to Strip")); + + QVBoxLayout *topLayout = new QVBoxLayout(this); + topLayout->setContentsMargins(0, 0, 0, 0); + numInput = new QSpinBox(this); + numInput->setRange(min, max); + numInput->setValue(current); + + QLabel *label = new QLabel(i18nc("@label:spinbox", "&Strip number:"), this); + label->setBuddy(numInput); + topLayout->addWidget(label); + topLayout->addWidget(numInput); + // A little bit extra space + topLayout->addStretch(10); + + QDialogButtonBox *buttonBox = new QDialogButtonBox(this); + buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + connect(buttonBox, &QDialogButtonBox::accepted, this, &ChooseStripNumDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &ChooseStripNumDialog::reject); + topLayout->addWidget(buttonBox); + + numInput->setFocus(); + } + + int getStripNumber() const + { + return numInput->value(); + } + +protected: + QSpinBox *numInput; +}; +// END choose a strip dialog + +StripSelector::StripSelector(QObject *parent) + : QObject(parent) +{ +} + +StripSelector::~StripSelector() +{ +} + +StripSelector *StripSelectorFactory::create(IdentifierType type) +{ + switch (type) { + case IdentifierType::NumberIdentifier: + return new NumberStripSelector(); + case IdentifierType::DateIdentifier: + return new DateStripSelector(); + case IdentifierType::StringIdentifier: + return new StringStripSelector(); + } + + return nullptr; +} + +StringStripSelector::StringStripSelector(QObject *parent) + : StripSelector(parent) +{ +} + +StringStripSelector::~StringStripSelector() +{ +} + +void StringStripSelector::select(const ComicData ¤tStrip) +{ + bool ok; + const QString strip = QInputDialog::getText(nullptr, + i18nc("@title:window", "Go to Strip"), + i18nc("@label:textbox", "Strip identifier:"), + QLineEdit::Normal, + currentStrip.current(), + &ok); + if (ok) { + Q_EMIT stripChosen(strip); + } + deleteLater(); +} + +NumberStripSelector::NumberStripSelector(QObject *parent) + : StripSelector(parent) +{ +} + +NumberStripSelector::~NumberStripSelector() +{ +} + +void NumberStripSelector::select(const ComicData ¤tStrip) +{ + QScopedPointer pageDialog( + new ChooseStripNumDialog(nullptr, currentStrip.current().toInt(), currentStrip.firstStripNum(), currentStrip.maxStripNum())); + if (pageDialog->exec() == QDialog::Accepted) { + Q_EMIT stripChosen(QString::number(pageDialog->getStripNumber())); + } + deleteLater(); +} + +DateStripSelector::DateStripSelector(QObject *parent) + : StripSelector(parent) +{ +} + +DateStripSelector::~DateStripSelector() +{ +} + +void DateStripSelector::select(const ComicData ¤tStrip) +{ + mFirstIdentifierSuffix = currentStrip.first(); + + KDatePicker *calendar = new KDatePicker; + calendar->setAttribute(Qt::WA_DeleteOnClose); // to have destroyed emitted upon closing + calendar->setMinimumSize(calendar->sizeHint()); + calendar->setDate(QDate::fromString(currentStrip.current(), QStringLiteral("yyyy-MM-dd"))); + + connect(calendar, &KDatePicker::dateSelected, this, &DateStripSelector::slotChosenDay); + connect(calendar, &KDatePicker::dateEntered, this, &DateStripSelector::slotChosenDay); + + // only delete this if the dialog got closed + connect(calendar, &KDatePicker::destroyed, this, &DateStripSelector::deleteLater); + calendar->show(); +} + +void DateStripSelector::slotChosenDay(const QDate &date) +{ + if (date <= QDate::currentDate()) { + QDate temp = QDate::fromString(mFirstIdentifierSuffix, QStringLiteral("yyyy-MM-dd")); + // only update if date >= first strip date, or if there is no first + // strip date + if (temp.isValid() || date >= temp) { + Q_EMIT stripChosen(date.toString(QStringLiteral("yyyy-MM-dd"))); + } + } +} diff --git a/applets/comic/stripselector.h b/applets/comic/stripselector.h new file mode 100644 index 00000000..8220e828 --- /dev/null +++ b/applets/comic/stripselector.h @@ -0,0 +1,59 @@ +/* + * SPDX-FileCopyrightText: 2012 Matthias Fuchs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef STRIP_SELECTOR_H +#define STRIP_SELECTOR_H + +#include + +#include "comicinfo.h" +#include "engine/types.h" + +class ComicData; + +/** + * Enables users to visually select a strip they want to navigate to. + * Subclasses implement different Selectors for the different comic + * types. + * @note use the StripSelectorFactory to retrieve an appropriate + * StripSelector + */ +class StripSelector : public QObject +{ + Q_OBJECT + +public: + ~StripSelector() override; + + /** + * Select a strip depending on the subclass + * @param currentStrip the currently active strip + * @note StripSelector takes care to delete itself + */ + virtual void select(const ComicData ¤tStrip) = 0; + +Q_SIGNALS: + /** + * @param strip the selected strip, can be empty + * + */ + void stripChosen(const QString &strip); + +protected: + explicit StripSelector(QObject *parent = nullptr); +}; + +/** + * Class to retrieve the correct StripSelector depending on the + * specified IdentifierType + */ +class StripSelectorFactory +{ +public: + static StripSelector *create(IdentifierType type); +}; + +#endif diff --git a/applets/comic/stripselector_p.h b/applets/comic/stripselector_p.h new file mode 100644 index 00000000..5e0b2090 --- /dev/null +++ b/applets/comic/stripselector_p.h @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: 2012 Matthias Fuchs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef STRIP_SELECTOR_P_H +#define STRIP_SELECTOR_P_H + +#include "stripselector.h" + +#include + +class StringStripSelector : public StripSelector +{ +public: + explicit StringStripSelector(QObject *parent = nullptr); + ~StringStripSelector() override; + + void select(const ComicData ¤tStrip) override; +}; + +class NumberStripSelector : public StripSelector +{ +public: + explicit NumberStripSelector(QObject *parent = nullptr); + ~NumberStripSelector() override; + + void select(const ComicData ¤tStrip) override; +}; + +class DateStripSelector : public StripSelector +{ + Q_OBJECT + +public: + explicit DateStripSelector(QObject *parent = nullptr); + ~DateStripSelector() override; + + void select(const ComicData ¤tStrip) override; + +private Q_SLOTS: + void slotChosenDay(const QDate &date); + +private: + QString mFirstIdentifierSuffix; +}; + +#endif diff --git a/applets/dict/CMakeLists.txt b/applets/dict/CMakeLists.txt new file mode 100644 index 00000000..8b132ef7 --- /dev/null +++ b/applets/dict/CMakeLists.txt @@ -0,0 +1,36 @@ +find_package(Qt6WebEngineQuick ${QT_MIN_VERSION} CONFIG) + +set_package_properties(Qt6WebEngineQuick PROPERTIES + PURPOSE "Needed by the dict applet" + URL "https://doc.qt.io/qt-6/qtwebengine-index.html" + TYPE OPTIONAL +) + +if (NOT Qt6WebEngineQuick_FOUND) + return() +endif() + +plasma_install_package(package org.kde.plasma_applet_dict) +# TODO: fix pluginid "org.kde.plasma_applet_dict" into properly namespaced id "org.kde.plasma.dict" + +ecm_install_icons( + ICONS sc-apps-accessories-dictionary.svgz + DESTINATION ${KDE_INSTALL_ICONDIR} +) + +####################################################################################### +add_definitions(-DTRANSLATION_DOMAIN="plasma_applet_org.kde.plasma_applet_dict") + +ecm_add_qml_module(dictplugin URI org.kde.plasma.private.dict) +target_sources(dictplugin PRIVATE + plugin/dict_plugin.cpp + plugin/dict_object.cpp + plugin/dictionariesmodel.cpp +) +target_link_libraries(dictplugin PRIVATE + KF6::I18n + Qt::Quick + Qt::WebEngineQuick + engine_dict_static +) +ecm_finalize_qml_module(dictplugin) diff --git a/applets/dict/Messages.sh b/applets/dict/Messages.sh new file mode 100755 index 00000000..a62e11fc --- /dev/null +++ b/applets/dict/Messages.sh @@ -0,0 +1,2 @@ +#! /usr/bin/env bash +$XGETTEXT `find . -name \*.qml -o -name \*.cpp` -o $podir/plasma_applet_org.kde.plasma_applet_dict.pot diff --git a/applets/dict/package/contents/config/config.qml b/applets/dict/package/contents/config/config.qml new file mode 100644 index 00000000..5cc74949 --- /dev/null +++ b/applets/dict/package/contents/config/config.qml @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2017 David Faure + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.1 + +import org.kde.plasma.configuration 2.0 + +ConfigModel { + ConfigCategory { + name: i18nc("@title", "Dictionaries") + icon: "accessories-dictionary" + source: "ConfigDictionaries.qml" + } +} diff --git a/applets/dict/package/contents/config/main.xml b/applets/dict/package/contents/config/main.xml new file mode 100644 index 00000000..091ed76a --- /dev/null +++ b/applets/dict/package/contents/config/main.xml @@ -0,0 +1,13 @@ + + + + + + + all + + + diff --git a/applets/dict/package/contents/ui/AvailableDictSheet.qml b/applets/dict/package/contents/ui/AvailableDictSheet.qml new file mode 100644 index 00000000..0c22aa3d --- /dev/null +++ b/applets/dict/package/contents/ui/AvailableDictSheet.qml @@ -0,0 +1,77 @@ +/* + SPDX-FileCopyrightText: 2013 Kai Uwe Broulik + SPDX-FileCopyrightText: 2022 Fushan Wen + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +import QtQuick 2.15 +import QtQuick.Controls 2.15 as QQC2 +import QtQuick.Layouts 1.15 + +import org.kde.kirigami 2.20 as Kirigami +import org.kde.kitemmodels 1.0 as KItemModels + +Kirigami.OverlaySheet { + id: sheet + + readonly property alias view: sheetListView + + onOpened: { + filter.text = ""; + filter.forceActiveFocus() + } + + // Need to manually set the parent when using this in a Plasma config dialog + parent: sheet.parent.parent + + header: ColumnLayout { + Layout.preferredWidth: sheetListView.implicitWidth + + Kirigami.Heading { + Layout.fillWidth: true + text: i18n("Add More Dictionaries") + textFormat: Text.PlainText + wrapMode: Text.Wrap + } + Kirigami.SearchField { + id: filter + Layout.fillWidth: true + } + } + + footer: QQC2.DialogButtonBox { + standardButtons: QQC2.DialogButtonBox.Ok + onAccepted: sheet.close() + } + + ListView { + id: sheetListView + focus: true // keyboard navigation + activeFocusOnTab: true // keyboard navigation + implicitWidth: Kirigami.Units.gridUnit * 25 + + reuseItems: true + + model: KItemModels.KSortFilterProxyModel { + sourceModel: dictionariesModel + filterRoleName: "EditRole" // id + filterString: filter.text + filterCaseSensitivity: Qt.CaseInsensitive + } + + delegate: QQC2.CheckDelegate { + id: checkbox + width: sheetListView.width + focus: true // keyboard navigation + text: `${model.id} (${model.description})` + checked: model.checked + onToggled: { + model.checked = checked; + sheetListView.currentIndex = index; // highlight + sheetListView.forceActiveFocus(); // keyboard navigation + } + highlighted: ListView.isCurrentItem + } + } +} diff --git a/applets/dict/package/contents/ui/ConfigDictionaries.qml b/applets/dict/package/contents/ui/ConfigDictionaries.qml new file mode 100644 index 00000000..da0399eb --- /dev/null +++ b/applets/dict/package/contents/ui/ConfigDictionaries.qml @@ -0,0 +1,105 @@ +/* + * SPDX-FileCopyrightText: 2017 David Faure + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.15 +import QtQuick.Controls 2.5 +import QtQuick.Layouts 1.15 +import org.kde.kirigami 2.19 as Kirigami +import org.kde.kcmutils as KCM +import org.kde.plasma.private.dict 1.0 + +KCM.ScrollViewKCM { + + property string cfg_dictionary: "" + + DictionariesModel { + id: dictionariesModel + Component.onCompleted: enabledDicts = cfg_dictionary + onEnabledDictsChanged: cfg_dictionary = enabledDicts + } + + AvailableDictSheet { + id: sheet + } + + view: ListView { + id: listView + + model: dictionariesModel.enabledDictModel + reuseItems: true + + displaced: Transition { + NumberAnimation { + properties: "y" + duration: Kirigami.Units.longDuration + } + } + + delegate: DictItemDelegate { + width: listView.width + view: listView + + onMoveRequested: (oldIndex, newIndex) => { + dictionariesModel.move(oldIndex, newIndex); + } + onRemoved: index => { + dictionariesModel.setDisabled(index); + } + } + + Loader { + active: dictionariesModel.loading || sheet.view.count === 0 || listView.count === 0 + asynchronous: true + + anchors.centerIn: parent + visible: active + + sourceComponent: dictionariesModel.loading ? loadingPlaceHolder : (sheet.view.count === 0 ? errorPlaceHolder : emptyPlaceholder) + + Component { + id: loadingPlaceHolder + + Kirigami.LoadingPlaceholder { + anchors.centerIn: parent + } + } + + Component { + id: errorPlaceHolder + + Kirigami.PlaceholderMessage { + anchors.centerIn: parent + width: root.width - (Kirigami.Units.largeSpacing * 4) + icon.name: "network-disconnect" + text: i18n("Unable to load dictionary list") + explanation: i18nc("%2 human-readable error string", "Error code: %1 (%2)", dictionariesModel.errorCode, dictionariesModel.errorString) + } + } + + Component { + id: emptyPlaceholder + + Kirigami.PlaceholderMessage { + anchors.centerIn: parent + width: root.width - (Kirigami.Units.largeSpacing * 4) + icon.name: "edit-none" + text: i18n("No dictionaries") + } + } + } + } + + footer: RowLayout { + Button { + enabled: sheet.view.count > 0 + text: i18n("Add More…") + icon.name: "list-add" + onClicked: { + sheet.open(); + } + } + } +} diff --git a/applets/dict/package/contents/ui/DictItemDelegate.qml b/applets/dict/package/contents/ui/DictItemDelegate.qml new file mode 100644 index 00000000..2e5e7fe1 --- /dev/null +++ b/applets/dict/package/contents/ui/DictItemDelegate.qml @@ -0,0 +1,66 @@ +/* + SPDX-FileCopyrightText: 2020 Ismael Asensio + SPDX-FileCopyrightText: 2022 Fushan Wen + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +import QtQuick 2.15 +import QtQuick.Controls 2.15 as QQC2 +import QtQuick.Layouts 1.15 + +import org.kde.kirigami 2.20 as Kirigami +import org.kde.kirigami.delegates as KD + +// External item required to make Kirigami.ListItemDragHandle work +Item { + id: delegate + + property ListView view + + implicitHeight : dictItem.implicitHeight + + signal removed(int index) + signal moveRequested(int oldIndex, int newIndex) + + Kirigami.SwipeListItem { + id: dictItem + + // Don't need highlight, hover, or pressed effects + highlighted: false + hoverEnabled: false + down: false + + text: model.id + Accessible.description: model.description + + contentItem: RowLayout { + Kirigami.ListItemDragHandle { + listItem: dictItem + listView: delegate.view + onMoveRequested: (oldIndex, newIndex) => { + delegate.moveRequested(oldIndex, newIndex); + } + } + + KD.TitleSubtitle { + Layout.fillWidth: true + + selected: dictItem.highlighted + + title: dictItem.text + subtitle: dictItem.Accessible.description + } + } + + actions: [ + Kirigami.Action { + text: i18n("Delete") + icon.name: "entry-delete" + onTriggered: { + delegate.removed(index); + } + } + ] + } +} diff --git a/applets/dict/package/contents/ui/main.qml b/applets/dict/package/contents/ui/main.qml new file mode 100644 index 00000000..fbf237fd --- /dev/null +++ b/applets/dict/package/contents/ui/main.qml @@ -0,0 +1,159 @@ +/* + * SPDX-FileCopyrightText: 2017 David Faure + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.15 +import QtQuick.Layouts 1.1 +import org.kde.plasma.components 3.0 as PlasmaComponents3 +import org.kde.kirigami 2.20 as Kirigami +import org.kde.plasma.extras 2.0 as PlasmaExtras +import org.kde.plasma.plasmoid 2.0 +import QtWebEngine +import QtQuick.Controls + +import org.kde.plasma.private.dict 1.0 + +PlasmoidItem { + id: root + switchWidth: Kirigami.Units.gridUnit * 10 + switchHeight: Kirigami.Units.gridUnit * 10 + + fullRepresentation: ColumnLayout { + Keys.forwardTo: input + + DictObject { + id: dict + selectedDictionary: plasmoid.configuration.dictionary + // Activate the busy indicator, and deactivate it when page is loaded. + onSearchInProgress: placeholder.opacity = 1; + onDefinitionFound: html => { + web.loadHtml(html); + placeholder.opacity = 0; + } + } + + RowLayout { + focus: true + Layout.alignment: Qt.AlignTop + Layout.fillWidth: true + PlasmaExtras.SearchField { + id: input + focus: root.expanded && !Kirigami.InputMethod.willShowOnActive + placeholderText: i18nc("@info:placeholder", "Enter word to define here…") + Layout.fillWidth: true + Layout.minimumWidth: Kirigami.Units.gridUnit * 12 + onAccepted: { + if (input.text === "") { + web.visible = false; + placeholder.opacity = 0; + web.loadHtml(""); + } else { + web.visible = Qt.binding(() => !dict.hasError); + dict.lookup(input.text); + } + } + } + PlasmaComponents3.Button { + id: configureButton + + display: PlasmaComponents3.AbstractButton.IconOnly + hoverEnabled: true + icon.name: "configure" + text: Plasmoid.internalAction("configure").text + + PlasmaComponents3.ToolTip.delay: Kirigami.Units.toolTipDelay + PlasmaComponents3.ToolTip.text: configureButton.text + PlasmaComponents3.ToolTip.visible: configureButton.hovered + + onClicked: Plasmoid.internalAction("configure").trigger(); + } + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.minimumHeight: input.Layout.minimumWidth + + WebEngineView { + id: web + anchors.fill: parent + visible: false + + zoomFactor: 1 + profile: dict.webProfile + property Menu contextMenu: Menu { + Repeater { + model: [ + WebEngineView.Back, + WebEngineView.Forward, + WebEngineView.Copy, + ] + MenuItem { + text: web.action(modelData).text + enabled: web.action(modelData).enabled + onClicked: web.action(modelData).trigger() + icon.name: web.action(modelData).iconName + display: MenuItem.TextBesideIcon + } + } + } + onContextMenuRequested: request => { + request.accepted = true; + contextMenu.popup(); + } + } + + Item { + id: placeholder + anchors.fill: parent + opacity: 0 + + Loader { + active: placeholder.visible + anchors.fill: parent + asynchronous: true + + sourceComponent: dict.hasError ? errorPlaceholder : loadingPlaceholder + } + + Behavior on opacity { + NumberAnimation { + easing.type: Easing.InOutQuad + duration: Kirigami.Units.veryLongDuration + } + } + } + + Component { + id: loadingPlaceholder + + Rectangle { + anchors.fill: parent + color: web.backgroundColor + + PlasmaComponents3.BusyIndicator { + anchors.centerIn: parent + } + } + } + + Component { + id: errorPlaceholder + + Item { + anchors.fill: parent + + PlasmaExtras.PlaceholderMessage { + width: parent.width - Kirigami.Units.gridUnit * 2 // For text wrap + anchors.centerIn: parent + iconName: "network-disconnect" + text: i18n("Unable to load definition") + explanation: i18nc("%2 human-readable error string", "Error code: %1 (%2)", dict.errorCode, dict.errorString) + } + } + } + } + } +} diff --git a/applets/dict/package/metadata.json b/applets/dict/package/metadata.json new file mode 100644 index 00000000..bd949126 --- /dev/null +++ b/applets/dict/package/metadata.json @@ -0,0 +1,149 @@ +{ + "KPackageStructure": "Plasma/Applet", + "KPlugin": { + "Authors": [ + { + "Email": "plasma-devel@kde.org", + "Name": "The Plasma Team", + "Name[ar]": "فريق بلازما", + "Name[ast]": "L'equipu de Plasma", + "Name[az]": "Plasma komandası", + "Name[bg]": "Екипът на Plasma", + "Name[ca@valencia]": "L'equip de Plasma", + "Name[ca]": "L'equip del Plasma", + "Name[cs]": "Team Plasma", + "Name[da]": "Plasma-holdet", + "Name[de]": "Das Plasma-Team", + "Name[en_GB]": "The Plasma Team", + "Name[eo]": "La Plasma Teamo", + "Name[es]": "El equipo de Plasma", + "Name[eu]": "Plasma taldea", + "Name[fi]": "Plasma-työryhmä", + "Name[fr]": "L'équipe de Plasma", + "Name[gl]": "O equipo de Plasma", + "Name[he]": "צוות פלזמה", + "Name[hu]": "A Plasma fejlesztői", + "Name[ia]": "Le equipa de Plasma", + "Name[id]": "Tim Plasma", + "Name[is]": "Plasma-þróunarhópurinn", + "Name[it]": "La squadra di Plasma", + "Name[ja]": "Plasma チーム", + "Name[ka]": "Plasma-ის გუნდი", + "Name[ko]": "Plasma 팀", + "Name[lt]": "Plasma komanda", + "Name[lv]": "„Plasma“ komanda", + "Name[nl]": "Het team van Plasma", + "Name[nn]": "Utviklingslaget for Plasma", + "Name[pl]": "Zespół Plazmy", + "Name[pt]": "A Equipa do Plasma", + "Name[pt_BR]": "A equipe do Plasma", + "Name[ro]": "Echipa Plasma", + "Name[ru]": "Команда разработчиков Plasma", + "Name[sk]": "Tím Plasma", + "Name[sl]": "Ekipa Plasme", + "Name[sv]": "Plasma-gruppen", + "Name[tr]": "Plasma Takımı", + "Name[uk]": "Команда розробників Плазми", + "Name[vi]": "Đội Plasma", + "Name[x-test]": "xxThe Plasma Teamxx", + "Name[zh_CN]": "The Plasma Team", + "Name[zh_TW]": "Plasma 團隊" + } + ], + "BugReportUrl": "https://bugs.kde.org/enter_bug.cgi?product=kdeplasma-addons&component=dictionary", + "Category": "Language", + "Description": "Look up the meaning of words and their translation into different languages", + "Description[ar]": "انظر معاني الكلمات و ترجمتها بلغات مختلفة", + "Description[az]": "Müxtəlif dillərdə sözlərin izahına və tərcüməsinə baxın", + "Description[bg]": "Търсене на значението на думите и превод на различни езици", + "Description[ca@valencia]": "Busca el significat de paraules i la seua traducció en diferents idiomes", + "Description[ca]": "Cerca el significat de paraules i la seva traducció en diferents idiomes", + "Description[cs]": "Vyhledat význam slov a jejich překlad do různých jazyků", + "Description[da]": "Se pÃ¥ betydningen af ord og deres oversættelse til andre sprog", + "Description[de]": "Die Bedeutung von Wörtern und ihre Übersetzung in verschiedene Sprachen nachschlagen", + "Description[en_GB]": "Look up the meaning of words and their translation into different languages", + "Description[eo]": "Serĉi la signifon de vortoj kaj ilian tradukon en malsamajn lingvojn", + "Description[es]": "Buscar el significado de palabras y su traducción a otros idiomas", + "Description[eu]": "Bilatu hitzen esanahia eta beraien itzulpena hizkuntza ezberdinetan", + "Description[fi]": "Tarkista sanojen merkitykset ja käännökset eri kielille", + "Description[fr]": "Rechercher le sens des mots et leur traduction dans différentes langues.", + "Description[gl]": "Busca o significado das palabras e a tradución a distintos idiomas", + "Description[he]": "חיפוש משמעות של מילים ותרגומן לשפות שונות", + "Description[hu]": "A szavak jelentésének és különböző nyelvekre való fordításának keresése", + "Description[ia]": "Cerca le signification de parolas lor traduction in linguages differente", + "Description[id]": "Cari arti kata dan terjemahannya ke dalam berbagai bahasa", + "Description[is]": "Fletta upp skilgreiningum orða og þýðingum þeirra á ýmsar tungur", + "Description[it]": "Cerca il significato delle parole e la loro traduzione in diverse lingue", + "Description[ja]": "単語の意味や翻訳を調べます", + "Description[ka]": "მოძებნეთ სიტყვების მნიშვნელობა და გადათარგმნეთ ისინი სხვადასხვა ენებზე", + "Description[ko]": "단어의 뜻을 찾고 다른 언어로 표시하기", + "Description[lt]": "Žodžių reikÅ¡mių paieÅ¡ka ir jų vertimas į skirtingas kalbas", + "Description[lv]": "Uzziniet vārdu nozÄ«mes un iegÅ«stiet to tulkojumus dažādās valodās", + "Description[nl]": "Zoek de betekenis van woorden en hun vertaling in verschillende talen op", + "Description[nn]": "SlÃ¥ opp ordtydingar og omsetjingar", + "Description[pl]": "Wyszukuje znaczeń wyrazów i ich tłumaczeń na inne języki", + "Description[pt]": "Procurar o significado das palavras e a sua tradução para várias línguas", + "Description[pt_BR]": "Procura o significado de palavras e a tradução para vários idiomas", + "Description[ro]": "Caută semnificația cuvintelor și traducerea acestora în diferite limbi", + "Description[ru]": "Поиск значений слов и их перевод на другие языки", + "Description[sk]": "Vyhľadajte význam slov a ich preklad do rôznych jazykov", + "Description[sl]": "Poišči pomen besed in njihove prevode v različne jezike", + "Description[sv]": "SlÃ¥ upp betydelsen hos ord och deras översättning till olika sprÃ¥k", + "Description[tr]": "Sözcüklerin anlamlarına ve diğer dillerdeki çevirilerine bak", + "Description[uk]": "Погляньте на значення слів та на їхній переклад іншими мовами", + "Description[vi]": "Tra nghÄ©a cá»§a các từ và phần dịch cá»§a chúng sang các ngôn ngữ khác nhau", + "Description[x-test]": "xxLook up the meaning of words and their translation into different languagesxx", + "Description[zh_CN]": "查找单词的含义和翻译", + "Description[zh_TW]": "查詢詞彙的意思以及它們在不同語言的翻譯", + "Icon": "accessories-dictionary", + "Id": "org.kde.plasma_applet_dict", + "License": "GPL-2.0+", + "Name": "Dictionary", + "Name[ar]": "قاموس", + "Name[ast]": "Diccionariu", + "Name[az]": "Lüğət", + "Name[bg]": "Речник", + "Name[ca@valencia]": "Diccionari", + "Name[ca]": "Diccionari", + "Name[cs]": "Slovník", + "Name[da]": "Ordbog", + "Name[de]": "Wörterbuch", + "Name[en_GB]": "Dictionary", + "Name[eo]": "Vortaro", + "Name[es]": "Diccionario", + "Name[eu]": "Hiztegia", + "Name[fi]": "Sanakirja", + "Name[fr]": "Dictionnaire", + "Name[gl]": "Dicionario", + "Name[he]": "מילון", + "Name[hu]": "Szótár", + "Name[ia]": "Dictionario", + "Name[id]": "Kamus", + "Name[is]": "Orðasafn", + "Name[it]": "Dizionario", + "Name[ja]": "辞書", + "Name[ka]": "ლექსიკონი", + "Name[ko]": "사전", + "Name[lt]": "Žodynas", + "Name[lv]": "VārdnÄ«ca", + "Name[nl]": "Woordenboek", + "Name[nn]": "Ord­bok", + "Name[pl]": "Słownik", + "Name[pt]": "Dicionário", + "Name[pt_BR]": "Dicionário", + "Name[ro]": "Dicționar", + "Name[ru]": "Словарь", + "Name[sk]": "Slovník", + "Name[sl]": "Slovar", + "Name[sv]": "Ordlista", + "Name[ta]": "அகரமுதலி", + "Name[tr]": "Sözlük", + "Name[uk]": "Словник", + "Name[vi]": "Từ điển", + "Name[x-test]": "xxDictionaryxx", + "Name[zh_CN]": "词典", + "Name[zh_TW]": "字典", + "Website": "https://kde.org/plasma-desktop/" + }, + "X-Plasma-API-Minimum-Version": "6.0" +} diff --git a/applets/dict/plugin/dict_object.cpp b/applets/dict/plugin/dict_object.cpp new file mode 100644 index 00000000..6d86ed79 --- /dev/null +++ b/applets/dict/plugin/dict_object.cpp @@ -0,0 +1,100 @@ +/* + * SPDX-FileCopyrightText: 2017 David Faure + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "dict_object.h" +#include +#include +#include +#include +#include + +class DictSchemeHandler : public QWebEngineUrlSchemeHandler +{ + Q_OBJECT +public: + DictSchemeHandler(QObject *parent) + : QWebEngineUrlSchemeHandler(parent) + { + } + void requestStarted(QWebEngineUrlRequestJob *job) override + { + job->fail(QWebEngineUrlRequestJob::UrlInvalid); + const QString word = job->requestUrl().path(); + Q_EMIT wordClicked(word); + } + +Q_SIGNALS: + void wordClicked(const QString &word); +}; + +DictObject::DictObject(QObject *parent) + : QObject(parent) + , m_selectedDict(QStringLiteral("wn")) +{ + m_webProfile = new QQuickWebEngineProfile(this); + auto *schemeHandler = new DictSchemeHandler(this); + connect(schemeHandler, &DictSchemeHandler::wordClicked, this, &DictObject::lookup); + connect(&m_engine, &DictEngine::definitionRecieved, this, [this](const QString &html) { + Q_EMIT definitionFound(html); + }); + connect(&m_engine, &DictEngine::dictErrorOccurred, this, &DictObject::slotDictErrorOccurred); + m_webProfile->installUrlSchemeHandler("dict", schemeHandler); +} + +void DictObject::lookup(const QString &word) +{ + m_hasError = false; + Q_EMIT hasErrorChanged(); + + if (!word.isEmpty()) { + // Look up new definition + Q_EMIT searchInProgress(); + m_engine.requestDefinition(word, m_selectedDict.toUtf8()); + } +} + +QString DictObject::selectedDictionary() const +{ + return m_selectedDict; +} + +void DictObject::setSelectedDictionary(const QString &dict) +{ + m_selectedDict = dict; +} + +QQuickWebEngineProfile *DictObject::webProfile() const +{ + return m_webProfile; +} + +bool DictObject::hasError() const +{ + return m_hasError; +} + +QAbstractSocket::SocketError DictObject::errorCode() const +{ + return m_errorCode; +} + +QString DictObject::errorString() const +{ + return m_errorString; +} + +void DictObject::slotDictErrorOccurred(QAbstractSocket::SocketError socketError, const QString &errorString) +{ + m_hasError = true; + m_errorCode = socketError; + m_errorString = errorString; + + Q_EMIT errorCodeChanged(); + Q_EMIT errorStringChanged(); + Q_EMIT hasErrorChanged(); +} + +#include "dict_object.moc" diff --git a/applets/dict/plugin/dict_object.h b/applets/dict/plugin/dict_object.h new file mode 100644 index 00000000..e5fdeae4 --- /dev/null +++ b/applets/dict/plugin/dict_object.h @@ -0,0 +1,73 @@ +/* + * SPDX-FileCopyrightText: 2017 David Faure + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ +#ifndef DICT_OBJECT_H +#define DICT_OBJECT_H + +#include "../../dict/dictengine.h" +#include +#include + +class DictObject : public QObject +{ + Q_OBJECT + Q_PROPERTY(QQuickWebEngineProfile *webProfile READ webProfile CONSTANT) + Q_PROPERTY(QString selectedDictionary READ selectedDictionary WRITE setSelectedDictionary) + + /** + * @return @c true if there is a network error when finding the definition, + * @c false otherwise. + */ + Q_PROPERTY(bool hasError READ hasError NOTIFY hasErrorChanged) + + /** + * @return the type of the last socket error + */ + Q_PROPERTY(QAbstractSocket::SocketError errorCode READ errorCode NOTIFY errorCodeChanged) + + /** + * @return a human-readable description of the last socket error + */ + Q_PROPERTY(QString errorString READ errorString NOTIFY errorStringChanged) + +public: + explicit DictObject(QObject *parent = nullptr); + + QQuickWebEngineProfile *webProfile() const; + + QString selectedDictionary() const; + void setSelectedDictionary(const QString &dict); + + bool hasError() const; + QAbstractSocket::SocketError errorCode() const; + QString errorString() const; + +public Q_SLOTS: + void lookup(const QString &word); + +Q_SIGNALS: + void searchInProgress(); + void definitionFound(const QString &html); + + void hasErrorChanged(); + void errorCodeChanged(); + void errorStringChanged(); + +private Q_SLOTS: + void slotDictErrorOccurred(QAbstractSocket::SocketError socketError, const QString &errorString); + +private: + QString m_dataEngineName; + QString m_selectedDict; + + DictEngine m_engine; + QQuickWebEngineProfile *m_webProfile; + + bool m_hasError = false; + QAbstractSocket::SocketError m_errorCode = QAbstractSocket::UnknownSocketError; + QString m_errorString; +}; + +#endif diff --git a/applets/dict/plugin/dict_plugin.cpp b/applets/dict/plugin/dict_plugin.cpp new file mode 100644 index 00000000..1519c271 --- /dev/null +++ b/applets/dict/plugin/dict_plugin.cpp @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2015 Dominik Haumann + * SPDX-FileCopyrightText: 2017 David Faure + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ +#include "dict_object.h" +#include "dictionariesmodel.h" + +// Qt +#include +#include + +#include +#include + +class DictPlugin : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") + +public: + void registerTypes(const char *uri) override + { + qRegisterMetaType(); + + qmlRegisterAnonymousType("", 1); + + qmlRegisterType(uri, 1, 0, "DictObject"); + qmlRegisterType(uri, 1, 0, "DictionariesModel"); + + QWebEngineUrlScheme::registerScheme(QWebEngineUrlScheme(QByteArrayLiteral("dict"))); + } +}; + +#include "dict_plugin.moc" diff --git a/applets/dict/plugin/dictionariesmodel.cpp b/applets/dict/plugin/dictionariesmodel.cpp new file mode 100644 index 00000000..6a455924 --- /dev/null +++ b/applets/dict/plugin/dictionariesmodel.cpp @@ -0,0 +1,314 @@ +/* + * SPDX-FileCopyrightText: 2017 David Faure + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "dictionariesmodel.h" +#include + +EnabledDictModel::EnabledDictModel(QObject *parent) + : QAbstractListModel(parent) +{ +} + +QVariant EnabledDictModel::data(const QModelIndex &index, int role) const +{ + if (!checkIndex(index)) { + return {}; + } + + const int row = index.row(); + + switch (role) { + case Qt::DisplayRole: + return m_enabledDicts[row].description; + case Qt::EditRole: + return m_enabledDicts[row].id; + default: + return {}; + } +} + +int EnabledDictModel::rowCount(const QModelIndex &index) const +{ + return index.isValid() ? 0 : m_enabledDicts.size(); +} + +QHash EnabledDictModel::roleNames() const +{ + return { + {Qt::DisplayRole, "description"}, + {Qt::EditRole, "id"}, + }; +} + +bool EnabledDictModel::moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild) +{ + if (sourceParent != destinationParent || sourceParent.isValid()) { + return false; + } + + const bool isMoveDown = destinationChild > sourceRow; + // QAbstractItemModel::beginMoveRows(): when moving rows down in the same parent, + // the rows will be placed before the destinationChild index. + if (!beginMoveRows(sourceParent, sourceRow + count - 1, sourceRow, destinationParent, isMoveDown ? destinationChild + 1 : destinationChild)) { + return false; + } + + for (int i = 0; i < count; i++) { + m_enabledDicts.move(isMoveDown ? sourceRow : sourceRow + i, destinationChild); + } + + endMoveRows(); + return true; +} + +void EnabledDictModel::appendDict(const AvailableDict &dict) +{ + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + + m_enabledDicts.append(dict); + + endInsertRows(); +} + +void EnabledDictModel::removeDict(int _index) +{ + if (_index < 0 || _index >= rowCount()) { + return; + } + + beginRemoveRows(QModelIndex(), _index, _index); + + m_enabledDicts.removeAt(_index); + + endRemoveRows(); +} + +DictionariesModel::DictionariesModel(QObject *parent) + : QAbstractListModel(parent) + , m_enabledDictModel(new EnabledDictModel(this)) +{ + connect(&m_engine, &DictEngine::dictErrorOccurred, this, &DictionariesModel::slotDictErrorOccurred); + connect(&m_engine, &DictEngine::dictsRecieved, this, [this](const QMap &dicts) { + beginResetModel(); + m_availableDicts = {}; + m_idIndexProxyMap.clear(); + m_availableDicts.resize(dicts.count()); + m_idIndexProxyMap.reserve(dicts.size()); + + int i = 0; + for (auto it = dicts.begin(), end = dicts.end(); it != end; ++it, ++i) { + m_availableDicts[i] = AvailableDict{it.key(), it.value()}; + m_idIndexProxyMap.emplace(it.key(), i); + } + endResetModel(); + + setEnabledDicts(m_enabledDicts); + }); + connect(&m_engine, &DictEngine::dictLoadingChanged, this, &DictionariesModel::slotDictLoadingChanged); + + m_engine.requestDicts(); +} + +QVariant DictionariesModel::data(const QModelIndex &index, int role) const +{ + const int row = index.row(); + switch (role) { + case Qt::DisplayRole: + return m_availableDicts[row].description; + case Qt::EditRole: + return m_availableDicts[row].id; + case Qt::CheckStateRole: + return m_availableDicts[row].enabled; + default: + break; + } + return QVariant(); +} + +bool DictionariesModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!checkIndex(index)) { + return false; + } + + const int row = index.row(); + + switch (role) { + case Qt::CheckStateRole: { + if (value.toBool()) { + setEnabled(m_availableDicts[row].id); + } else { + setDisabled(m_enabledDictIdList.indexOf(m_availableDicts[row].id)); + } + + return true; + } + + default: + return false; + } +} + +int DictionariesModel::rowCount(const QModelIndex &index) const +{ + if (index.isValid()) { + return 0; // flat model + } + return m_availableDicts.size(); +} + +QHash DictionariesModel::roleNames() const +{ + return { + {Qt::DisplayRole, "description"}, + {Qt::EditRole, "id"}, + {Qt::CheckStateRole, "checked"}, + }; +} + +QString DictionariesModel::enabledDicts() const +{ + return m_enabledDictIdList.join(QLatin1Char(',')); +} + +void DictionariesModel::setEnabledDicts(const QString &dicts) +{ + m_enabledDicts = dicts; + if (!dicts.isEmpty()) { + m_enabledDictIdList = dicts.split(QLatin1Char(','), Qt::SkipEmptyParts); + } else { + m_enabledDictIdList.clear(); + } + Q_EMIT enabledDictsChanged(); + + if (m_availableDicts.empty()) { + // Loading + return; + } + + std::vector enabledDictList; + enabledDictList.resize(m_enabledDictIdList.size()); + + for (std::size_t i = 0; i < m_availableDicts.size(); i++) { + auto &dict = m_availableDicts.at(i); + auto it = std::find_if(m_enabledDictIdList.cbegin(), m_enabledDictIdList.cend(), [&dict](const QString &id) { + return id == dict.id; + }); + const bool enabled = it != m_enabledDictIdList.cend(); + + if (dict.enabled != enabled) { + dict.enabled = enabled; + Q_EMIT dataChanged(index(i, 0), index(i, 0), {Qt::CheckStateRole}); + } + + if (enabled) { + enabledDictList[std::distance(m_enabledDictIdList.cbegin(), it)] = dict; + } + } + + for (const auto &dict : std::as_const(enabledDictList)) { + if (!dict.enabled) { + continue; + } + + m_enabledDictModel->appendDict(dict); + } +} + +QAbstractListModel *DictionariesModel::enabledDictModel() const +{ + return m_enabledDictModel; +} + +void DictionariesModel::setEnabled(const QString &dict) +{ + const auto it = m_idIndexProxyMap.find(dict); + if (it == m_idIndexProxyMap.end()) { + return; + } + + auto &d = m_availableDicts.at(it->second); + if (d.enabled) { + return; + } + + d.enabled = true; + Q_EMIT dataChanged(index(it->second, 0), index(it->second, 0), {Qt::CheckStateRole}); + + if (!m_enabledDictIdList.contains(d.id)) { + m_enabledDictIdList.append(d.id); + m_enabledDictModel->appendDict(d); + + Q_EMIT enabledDictsChanged(); + } +} + +void DictionariesModel::setDisabled(int _index) +{ + if (_index < 0 || _index >= m_enabledDictIdList.size()) { + return; + } + + m_enabledDictModel->removeDict(_index); + + const QString id = m_enabledDictIdList.takeAt(_index); + Q_EMIT enabledDictsChanged(); + + const auto it = m_idIndexProxyMap.find(id); + if (it == m_idIndexProxyMap.end()) { + return; + } + + auto &d = m_availableDicts.at(it->second); + d.enabled = false; + Q_EMIT dataChanged(index(it->second, 0), index(it->second, 0), {Qt::CheckStateRole}); +} + +void DictionariesModel::move(int oldIndex, int newIndex) +{ + if (oldIndex < 0 || oldIndex >= m_enabledDictIdList.size()) { + return; + } + if (newIndex < 0 || newIndex >= m_enabledDictIdList.size()) { + return; + } + + m_enabledDictModel->moveRows(QModelIndex(), oldIndex, 1, QModelIndex(), newIndex); + + m_enabledDictIdList.move(oldIndex, newIndex); + Q_EMIT enabledDictsChanged(); +} + +bool DictionariesModel::loading() const +{ + return m_loading; +} + +QAbstractSocket::SocketError DictionariesModel::errorCode() const +{ + return m_errorCode; +} + +QString DictionariesModel::errorString() const +{ + return m_errorString; +} + +void DictionariesModel::slotDictErrorOccurred(QAbstractSocket::SocketError socketError, const QString &errorString) +{ + m_errorCode = socketError; + m_errorString = errorString; + + Q_EMIT errorCodeChanged(); + Q_EMIT errorStringChanged(); +} + +void DictionariesModel::slotDictLoadingChanged(bool loading) +{ + m_loading = loading; + + Q_EMIT loadingChanged(); +} diff --git a/applets/dict/plugin/dictionariesmodel.h b/applets/dict/plugin/dictionariesmodel.h new file mode 100644 index 00000000..1a85b8c9 --- /dev/null +++ b/applets/dict/plugin/dictionariesmodel.h @@ -0,0 +1,118 @@ +/* + * SPDX-FileCopyrightText: 2017 David Faure + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ +#ifndef DICTIONARIES_MODEL_H +#define DICTIONARIES_MODEL_H + +#include "../../dict/dictengine.h" + +#include + +#include +#include +#include + +struct AvailableDict { + QString id; + QString description; + bool enabled = false; +}; + +class EnabledDictModel : public QAbstractListModel +{ +public: + explicit EnabledDictModel(QObject *parent = nullptr); + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + int rowCount(const QModelIndex &index = QModelIndex()) const override; + QHash roleNames() const override; + bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild) override; + + void appendDict(const AvailableDict &dict); + void removeDict(int _index); + +private: + QList m_enabledDicts; +}; + +class DictionariesModel : public QAbstractListModel +{ + Q_OBJECT + + /** + * @return the list of enabled dictionaries + */ + Q_PROPERTY(QString enabledDicts READ enabledDicts WRITE setEnabledDicts NOTIFY enabledDictsChanged) + + /** + * @return the model that contains all enabled dictionaries + */ + Q_PROPERTY(QAbstractListModel *enabledDictModel READ enabledDictModel CONSTANT) + + /** + * @return @c true if the engine is downloading dict list from + * the Internet, @c false otherwise. + */ + Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged) + + /** + * @return the type of the last socket error + */ + Q_PROPERTY(QAbstractSocket::SocketError errorCode READ errorCode NOTIFY errorCodeChanged) + + /** + * @return a human-readable description of the last socket error + */ + Q_PROPERTY(QString errorString READ errorString NOTIFY errorStringChanged) + +public: + explicit DictionariesModel(QObject *parent = nullptr); + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + int rowCount(const QModelIndex &index = QModelIndex()) const override; + QHash roleNames() const override; + + QString enabledDicts() const; + void setEnabledDicts(const QString &dicts); + + QAbstractListModel *enabledDictModel() const; + + Q_INVOKABLE void setEnabled(const QString &dict); + Q_INVOKABLE void setDisabled(int _index); + Q_INVOKABLE void move(int oldIndex, int newIndex); + + bool loading() const; + + QAbstractSocket::SocketError errorCode() const; + QString errorString() const; + +Q_SIGNALS: + void enabledDictsChanged(); + void loadingChanged(); + void errorCodeChanged(); + void errorStringChanged(); + +private Q_SLOTS: + void slotDictErrorOccurred(QAbstractSocket::SocketError socketError, const QString &errorString); + void slotDictLoadingChanged(bool loading); + +private: + void setAvailableDicts(const QVariantMap &data); + + DictEngine m_engine; + EnabledDictModel *m_enabledDictModel; + + std::vector m_availableDicts; + std::unordered_map m_idIndexProxyMap; + QString m_enabledDicts; + QStringList m_enabledDictIdList; + + bool m_loading = false; + QAbstractSocket::SocketError m_errorCode = QAbstractSocket::UnknownSocketError; + QString m_errorString; +}; + +#endif diff --git a/applets/dict/sc-apps-accessories-dictionary.svgz b/applets/dict/sc-apps-accessories-dictionary.svgz new file mode 100644 index 0000000000000000000000000000000000000000..15fb5026f225cab73ee3ff586b05473186ad4ca6 GIT binary patch literal 15551 zcmV;wJV3)AiwFp}(a~Z619M|7VQ_GBEn#D0Wpi_Ha%p9AEo5n9bZKvHVRCscb9QF{ z%v;-X8%L6T9(_eG{W6LH8+E_Xk|!j}ayY^hGqIk_zBwSENns5E3;+~KfBl}5-3@eu z07%MQ+hWX^SzVn~nU#6+Qq5O?z5O&RK3B`tbTNN@qDysBRP*s-GM!((KKa)_-=C(F zVznO4C!^V7UcEklbs4^{E~Y<9g`%TS?BsY*2{ ze*E`$#kaRti{-lb$L#v@^!s^H%HY52HP4DrDpkC_p3WwP*UqJvMR9tHC03s=Ulc`w z{moZr^VREE^tl_M@!ju)SXKaM_E?-$GeR3C=e`|xD@#$>#C=Ihbz>3sE7=T(Tb?^UCe8sgb@=Hq6a-Oi@-pAT+Eo_1Hqsb1lKn-|)FvX=D% zZ&u~JS`Xj-^WEm*sVXPyNrw%XuEwLQs<&=CD67v$pQ_c>Xk4v^?YO+(F*7X#)5+@- zNTgGYLbq$4O%~%1qZO7Ieq4O2hF8=1$7;G<3`fiL&0_iU@b9;Gm(_guk7m4P)+}|l zSw@x4|7B8ru4erSi|h5(_4=pkc3sWunb>tl1bJLyW51NW{dc!V8DF1_#t?F~SWc_e z>0~-ygV|_#C$IM3=ry~H6P*E(+{KZ&zH-^^&Eq+!@8mtM%flo&urQcQY)&q0{kV27LJHLjHX% zLkp}wUEiJQuTR=H78e(*3c{<+h*$<@gS|T`hF`RRD!mUm7`105y>F!;D-HVsK3Wkv zyN4B##_(eGC5q5FJVp`w7Eon$AzAO@qZFZY`ZbEMQO6xcw5;(cACYyBI5Pp$jlQ=~=b=5K)IIz8FyYUxNH!a%yb53cI@POuSoQj8 zs{dg5b5HdyE105wX4Qw3oN;-N?zKIld+q-?x=-Pt?o)iC?tfFWk19HC_t-s_I;{H? z|46#``eE#SNU0BlFxEMy5x)oAN`Fiz2M*~G$NP{rAI5p?*Z(S|tMnM9>n{v=V1HlN zdlvBSQ48#mU!?=`UrcA~YT3x$CXHBbR*k`FG`ktytxmRTsi#|Isg8^4yV3P(H66|0 z&aRi6HIH+lV3T*%=jmwOB#9;@<#M*YUH5ro!CFDXdIMj+Z~vV8j?pFcz`oH~r+Yx_LLd_SGYJGQALAT@aYK(vNX|($M zVmpfQ`RvFd!enRiQ9FvcZ%2Fa_V~(D+n;@0Ru`{NzS@J!M?gEaEcELW!|-o$7|qxn z#&RfiHbjMHo&kFD-2^iI;3Ijfv=PeT0&3H9$P;n6cpwj-f)*m`g(bR|^4HXk=| zK^cKAE@W#=S4znJhm|txj+7oT)O(*z*CPSa)@5gpo@AzoK%ejjl`?mvw7yI(deieq z-cvv z)gS{^D1w^7O@F+^XwpINQHM3pr4Caf_!w^zbrY^R7c8#wFiu`^^UCMK$W&Jii)UR;!Pr32)EL{YOpv zh-g@gczHcV&AFIW%h8;7W_t5ri6!=oFFyP))V}Y@hsAPIEjKIZ-mtW?a3(?i$ph}E z9j-68>iyzycr)7H{4a~eC!~&LG{J{P>swm@^oAWpjPLG`HOjkfn{m4j?(RJpUoV$9 z)zjJNu3Bzya&ew7UVW<8qseGJYB$}|Ks%uHi$A?uPA<;=``vr|`lnap@!5YC%bz)y z!=n$2YaB19;o)R_cClD~8m+&Xeu7eX1^$=YPcxXz=0Q7;?SYm_dxO9CD}c%P)0C5k zKdz^<+4tlt+XkCqvl;GjxZUI{UZ7vx$Q1rMn~tkh^GZwqC=0&j^{d>cLY1}OKy`KQ zzP;tGF|xH(l$o(X?GFps&!-=%i}l}^)5|GPeZ9PHcK2%7?&8(3RVaSn#vN+<&G$e3 zc(E9d*4N9?PgZn&Ke+VchTYg!f2Oa`vW*lk_JB^?vNAEppY%_ScRU2`O-*intc|d) zR#R!BuLp=Gy$LnCDBoCZyTm`?XDeC()r4PXqjmLXH8|BTMew$F+;21Q=_0@TmOmRc z@KI`XzRN=&;_+Vo^>}F2XmF>+{9@DgYxrzi_~Gli4W{Iu5S_kueG+Fd@mF(_;&@QrL2U;bkLxGT(c;6vIHX-xk5I}=8{==r8XP)KOzwm28Lu@YBcyeGyA?am_;Kpq({H zxA8_O@v2Pm10(GlvqSvA?j>IwXq}lu*8R+^kuB%}T&y(C6?l}a=f(`yQC!s+)YG~d za-kLggQ*>}_eN@wC?-N&uGjRn!p`fqsBQ;sV6T-T#=sG{cH}$_tu=2p>q`R_zsqR0 zWvfbQsu~%RXPdsN^=zd^rJ}EUtI^4baiMOm3M1$mT;MG{BJ8KpE&Br@L~jQ?%>d)l zMTWXUP{Co$IjvI6T?_iKw;Tc-p1E~mGDRwYadY{EeCr_9c4AHx87d{$Nk zl?Jdo#BT|MRyWzJK>L2siX~)q!y3p=fV55H9B3^sU-vw%nwWcbzXXtfNu1G>~ ztp__h$gVWCDT#ZFZ8&GQrUK#AJrbs9c{?+JCnkfpp;8?{W)|c$OAF`JI?_)ISSQR0 zRw@^5oDKU-uwy`|$8?x=%t1Dnb==)+-`%)11r;#2Urcl`_QHVc>ud~n;ak`DKNfG0iq*mLKFT2 zoS?Kh*OA}D#$l9MY}CUjVVs^u*(aX|i{(<%>|RQm-AhT`yOh+uOUXuEeJ8Q;n3LER z){gBGu)6965H*m^(T-H;+CL;#qHlX@AD|G6hJ$oAtdPX`o z*4RELbJ)jh({kPS&5Sc|%$q(ZcaT%m>0m&4qiom-C@vi41V1>$>8*9i?Ql9&Kfs(_ zbH;l(oxkzl`tu$qe^}!QiaJ=*@Z;D1p8R2rBVHWb(;j^EhctdL?8Y~+B-3XS9;%M& zko&6u;(T{NkSh?mY!}617sXr`!yKy?&kZ1@*Q{BbVF`mgrY>1nBcK?tmi37Sf&y-W z?-O;g0-Rx)3(RS16=UjXUdQ?*)dqpqN|9o;kdU4@$$G=e0IC8GphU{``x&{zJtXZz znLiru-D9B4KLyH=vIMtH4Lz#1(74F0bUMpu8=6q&siyS*QrqWq^ z_Z0ZyaC8sK9Q4h%?@<-?(J=VOIYZZ&<}!$++uvE=`uc&P^M(G`yO!wh*GkP-5PQp- zeU(zw57#;2DYhIHjny{ahS$xi0842;KfZ0gqwvt`wp+!PhvE0+hijcD1>ShL)rCkuG0w1>G@DLQ^QoQb_8{0e3f(S9J+SS9B+2}@ zT@X}>rk8o+zc$nZb$toXmk=Z0&D5d!9-OfO){ zJ!tB*Qe;A`q3+Sa(B}hf<-_{wnQ*qP34cjF`zzM}%dfi}kud{wU z#nEaPP47XN)lIv%=N>MU0cDLxjYHdW{6ZoOJZckEGmf7-31w|698Ak=QZ!o|Vc+s1 z@*cctLz$EnM$VG*-jfR2kg_PN0*sb#(t`aUFo>{xv&=~4BlM7RVI8WVI0z_)@RSsC z$^fpxwsQx_zZBW>1Hnwn1kFX9swr!m4JsqFlzompglK~zv4fMG2p_N1Z{zHl`m0e6MvUhpDI!%!F$rUH?K(pzXlN~ z3)V=MR6^Pg$_Pr45o%iO7BykLinSt5q~yHg(V_>+g0$Uvs?17%%knx6>gHKms!*Zs z40V}k>(-Z#I4xO+-;#y_PFmYWOAhNeD+U0{W5*7eL$tNd(!fFB#HnmXYmz+hT=JUE zvFiiF;YENOU@%Fg;N(yGght-r*v8eQs4~azy$s(pN=y)%jGAkzqzn8f2k?auC}FX! ztQ~ioHA@mth>VntcbVsfhlv6?VM+!G3~EReJ|I$v1tjLNk&3iJnlWI!q0dPp-cpGw z8lk*U$s|&d6pqvK98tU`Z%J#PxC{>il!0D6Qy_WfwaitLgn^C$qGM7_a&R8PHD(9V z9XkM^scz7K2~y4^zG?Yp#L3GlDzmHuC&g_{meX)G?2iH9}7HJw;t4;_9f{zgYJaBEC<6b>XbLTmCS3zcdrim_P!?&PGL zbTRm5^s-$T9K6-DXW@kcK#(s5XCVpLfnMcyjuwIl>k>5U8f)^7f7hZ>wt`X#7941U zygC6h0Vv*Y3ZZLb1=^5|o&-`Qyi)Rl)AG{Ol0DMl8aTEx_CqFe=6f-df1r}u*sz1H zp8BCF$K2QrgR|qG%?45^t;8wdw!Ut!b8DW-I82$g09v_No3@2QxmmcI=j~D^Wl`0G za!&rRM@tXN6R_ZJKvc~=xC~!}=5g9YByd4WAw4J0XgjQHgbOs02*k*0tK4b8??YbZBkMi&}=Ng*5GS__>o9tct`+L1`<(pV(I2%Vf_h%y)M4zR*%2}U0s zI~H_-$PEGWf`l9=P!OmpEv^LMfHPQFHJ>KW9Z|K!T9xbl+ANNyfOKFVeGNG=lCn6$ z5CyHyNsG(U>qRA57PYlbkiL;(DJBYYv8J(oQbx8A;Q)c5K;Dem)pIq`h)5tOL6z~T z)1;D>XI?G{1N+wXj`(}Xy8h#@M2f)_@&(CLd!|d*ZzTmh+eY#L^9}Ny2=!vDAFSIaJ3m?yzSW^fi#Yzy_vEir2ov7V$=CKE;tiAX1h@^nrw=gISu*H8kdeH3t2Ufg=>W zAVX|m%o2M@+>t!ZII6@1!iryWu9IvAxRfmUf=)R}T!BF+u-6jBnPp&2i!DIUS#k${ zv`cf5*1UX;0zG?`{eS`6AG0bpPtDOFR3xs0pl2EtmCgvzT%Tos{;nLAk}$?F;IsWo3``3PWy z0NWH39&XJHFf|1NGYOndP)W)KBj7?jCWX8rMA=e3H`MrSWNrhpOx;<)sG4$ZxswI= z>&qI+g#}ny{xEBHlJqGjSu>%A31MtYBSHjACCR|OHg!ppn5#S5K5W#dI?osQ&JuJ{ z1=!f+`Ttkl+3iS@99Q~P8oU!Ql>Glk)CwS)(pA61LRihkVg*v5b{Ev!%g=W#va_

L7|CJFH)a{UywO$8JdSQap%7$F73cy>obm+gL>i1K`5K)Kcu4e*E>u>o`UW z6{GU*E|s57=vREkOyC6Fi$zUUWnpAHY{%oL?NuL5f0;oWVNRk za3uMzRL+7Zj@4(IVYP)Rm>5NxyZqa#^&&hf!&2}QYAMDPcZwoFg>a=jTpXGs=gOLD zXa7i&>R{WjW(vPKyCp<;^sn9%)ITAK^HB1h&+0sq;5fc>BY>f`3OqKhDCdPW>vgZG z^Ax(odHM3v&zjN=29RN$XA&+Rfi9*z(;7ab&_?I?vk4821zMM(aYC<`7IBHYr+w7d zuY=;z!s%Q1LMV!XcP}I1VXb_F4Q~*ja6XSFXoG{U`8se&h>f~`A&8@tta-s22nVYI zL`s#E0h)R(N9#z9P0&RbY&jY|Wi9e708#P5*#fm#J~mBe`mBT_jf2)@md`pk(nNC$ zUTj@f6ir!a_H#XNRjz49PoVPUIiHKl(MaJd&*SOI^*LZej#$TKS@zwE)X|YrzzanI zbguk9nFkiYK8j7-;&`pXyzHF=FOFwJut915p_B%m(J{$_7n_&Z@ffFIHxVxBf(f&j z-O=RW_DVFS0_0?j5*tZQtlU(?@AGT|7t4F>0z+*8$M*m!trGeMm$lo)4u|!uRUB@n zU=A)*x}n8F@eCuDBU`(V4z%crO)lS1KqDi}nAu`2HTL?1#^C=5$SAr*<)R$5t#=sw8W_D00ip&YH*^G9}BPz`%B=ET)+!MZ!{6f!x3H@LC-kb zYh+^!DJ&__GkRf*WgnWC))PN%d=9}~AWENpYoOxg*DGvOL6F za`>^yJC}eJ3fwiw%JQ#ep;b=5!4-o=Z`^!j`6p*n^lV(Q**@)HzH^-m00deonxn~{ z*s-^cgmZ1^Qg*XmfE9LiTd4snCIec>G745~-ore5eMwKD&?j2# z$h$S0mJO@`Dai8>su0*sfk5=sXYS>hpE=nIQ*BLlKI&1Zq7lamro1-y!auFxh>ZE_ zDxcVZo~{-v0V;|DkZ0A@spnpD$tn(~_=mFUNB!*!R!n|XkVLRzv#Q(K6M_|;aK+hj zh84Luf_@(60Er+_R7K_!!8E~&<3Cr{$YN8yx6{R2_E^tZ{>WB1y@>U<$!9A;wp+RsMd_Wjz!nH+2jivJ`#ji@QhA?RKogZhHXL z>I5_?g{u1;i!Z3Nb_}suc7ExMbpy_7cM{|17b)~LjXZ%=)v#_j4I>5JovD&=pdfjl z!A~-V&iibF^)fEPS9v<0MB*nOcV77>&`2*zoSz0O2x1x$CYexST?TJjpj zA_}MZwKibd0X~i`G+>&;yZX&AtpN@vI-DRoveKynX3NQ)jF62v+m-ooY_&M?v%zd4 z`zTa6aii+2k@@+mBd*kskd<>o&ix2Y9nZjsjm4z|>0);(^0ThgPj`r*1VZb?0{d~E zgwSMLsgpz70OsiZ-%Z28I^Qr=#D$E zQV`dFJUdt4V=Df4?OYvC+c~8$29X{|QH6*@-5l3ai}g&1R8c47B1j@zQCk4yj2cTN z)Js+3OtHdD`fVCcZ>Qf@`-x2u0QqPtMGzOtrwdGXl2&+_Ld$($>9=ipf}RzpKa!%n z!p8?h)<&k@__%|^^=T)K;+{RHf8Vta=VaKi!7BW zAB_MZ4p>o0$WXq zw=2{ngq*SiTr2W#5#UM{gj_Ik^5X@Oojh{NT>diAF9jnNbzk#R?1G!wQV-~rto_z` zX_}I1+nI|KmW5klO~}U~$AmQ-h*>xmHp`K~J<7j1k2bsWZ@Ze!;{4m>I^FRHQB7@_ z8n12E>?gnQ2kpyUDGakyO>xJ~9vl2P9Z7lK;;6nN3^`uaAU!9p4lj<(?#_gCs@zJ!s2xE0>!dvD>-Vb~)6)t& ze^uDBQNh$kTPuMSJ#GLcly!fvp7GbJt6Tm+UES)_x>{%xV@}3I5sHR^F3x7=L^fVLG!lmHFHbJ1l-xaJ-RrAVQJQgB_n;cTwa+-t8hpAtyn}aq^&Z@>?Z*FL1fh ze5!^}kfLx`F^d$WnB?5>s}!T?cqZ$pb;T$qkwPLCid{|e#bZT`q6A>-#3<@;WNZ+m zm_)sHzEYp_CzN?5Mxh{Bg4Lx!L-l3IohU|;Am=H^q|B$iH}bzK=*R+MDPNh-?HuLN z%>-vq6vT8$i=Z~<6{K)dBMs+1Cjqs7_Njst1%=~Wk8+;WmfDL|^RV+@7U#X%`EM08--Rop zGw3_~%e#x;m9IF+@xCf4UsZ{m;A05F(<8{C6mi~PeLrE{iw^p}IytXY^b@I*-zxpA zKTtDARvV0C8(>=2XZ6VksFR-lI(qn9+T>QP_*$8~)Fo9l^W~>J3DtiV8sC$eJXwgU zJ5O~PfzzngZcWlbMgj^AMq4K0?ELYhny(Xf$SSZKm}`O(sx!3usLG(63n_6+u;A>~ zyn}oa+18?J{V@C75;v%XO92!Se3RpcD3*$dZqOuMp4X5NPX=%wBj;<1E=gd{>!+0OI zVSi0PxZj55s{ww0)6|De)7y(JGs^vbN&Ok9^sC%2^8@$Ges;eor<+;VHQ3Jz-RD6B zMEpN36XlB}jK!g&TroX0q!_SHKRd?9!QF%lB|q|jMtbsImnqqCB%-OQcm_#N) z;y%kWT*z_=6?fiMF{z_WMwqur9lmECS6p#y|4M8~P+9M$1Z`&ctp<6d#+!49bwTFg zd4g8Ml|;mZkmJ5kBS-@aaroheN>!)!FVj?+{$AoyE3-dFwNR0KqJYSeRs>1QKvl04 ztRH1-Rd88 zy2%-8O0?*91<7hKq>@k{WAboiR;vJBMIj(5mc#Osmk4B*>FHm-4N^M}CWrFeJ%t`xX!Uj`1j_Q(ny zdS^Z!?EiQ-V4CZ*_P>44X^#H>mj^If{Ql$l0Hb}J4=~2FKENiiq}~zs0XCe!LdO&W z=Br9lL{6EWqo!3Cxueixa%i7X7c_k-<^5py`PXC`)kbPLN;%Tt!l{~aHKx5ziik>L z?=|w}l1u0>1UsZsSdtoAG2?G@D9d-RU~*ZDZ>Os|>Iv2!58mz6Pmey^+-?#DDvrTL zLHACZoC;S^dfG-Z_985MWHZ%zx8a-9$70GnLh@nVayLbcEu<caKm!aJOugcRT{x>3*YW)5$$4dzk0T&?M1HPwqK~~(O^^2F{Qz%!*1q&d ze7PQjGV@B&-GVaZhcz)Xf1HV#^+^+>I9+C?nv2=IijQ;Dx_IYancK&OGDL%jDQi_} zMnNMX#P+R7#tgzzlC0voO9dNYThs!_iWwzcBz+?G5co|6E1xA7>#=S20e-4}9dHp= zyCjZP*+({Eo+_zwxv@<=P268HUlOgvR9K}G?HqdqU@$TFv#&aQRjI}bF+@V6CbDE- zCC7se%TigQYWciGF{P*|vXdkZMWs6?BAP@x#jedXAyuemjvjm~#(ErDHeQpYM5e61 zHn-AgSp8Dtx%67ppz>HdezhS_KGweQu5n#e-{nmKo2JY7rgNx$)C!DKy{~^0NR?z? zxVRKJ8&QoIz%cZYnbdGP6S9V-?AY9j5`da<0ea)I)v%*qL+5>&1zv7OsBs}xxn)A; zjJ(-RcDZ~}7aEqu=0Yn>Tk(T=d97;*ofR|(Q&6%40xX!zfxcKUH<%u0i8~Q%5~DH5 zB~Yb}c=RjObB;_LxfVY;>mZts1DT8Rn=IE~CWMuxazaUcdAr?hQw1bNy)nRFwfVMo zN6O|-!}dp~YY$~#+CE#M>oAsVGMV9kY#w_S$5XCFLLA5SrFvEwB-*R-q(&e!$6j3n zqoi!6iZ}{V&HG`v@c=WhI23F#9EVrVU~kh0hx#z?Ys6R9wbSi%dI>JCR#DBhAnlOu zb*DL{%zh{D5mWl%Aei<(_?KH@-D-r(cW0<71APD?QYj! z9-i~TQqK1iBK=uQIUbQ%`>v(T`Ny@C>*FltO3zx#emUx+MNy00It8pG*vAw@ThhXG zF2I*KP2MQ1P(=Xn)v6rHNee>)F+n=B7A%c!1AU?PZ{d1P+!ew_0_s^ z76FPERT_93-I9I%(mqi_W2eP^Oo;V*ZB3t>7ua_cc(Upwpmpg}lY6V?xJ@;{7G+sA zA>O@8G^jRb;+7;zbVP;oxgtYDjN?o}V@ZSMovDy&<^%J6T&}b|Af>G&@nmhq5cZCC zuNi7?@h%!Le$xyoIwd#6gBfb&Bd)A9ePQ{mZ+&@7Voz2);gVCwpG?9`l^No zPCCH@>6u#k@IfWnx0P3}ZU+Hc=mYZ)A zmZZDmjejEpq^=)fXT*n0)-8+Riz=ZasNbSnPFc2B99p_vZ>ESioqC*T>J`i%I_cEe!nUP(@&7t!|hq-mFnH?9XANZp~#yn+l){_@PN&s521`Bi-?!M4Bk9K3fYd7Yzc4PfFGY+f% zutsB-k24y(J!v$0)2GJODYQievO2QRL6k09s^V4q!a@bSimN5LH;t%y>U13~LOLX1 ziK>98PCyqqr`=pUwKyfy7B@Yj3r6^R6bhu3?@*7_8CA{zOa%kVr|#}5NSzuGLG2PW zXf;z)aHxO-y9;U5S-DZ@FJ4}z;mIdzO>ED0qOVwWHhkrx%Z%U;klIz1ti9~9p{oxo zdM-Q2n@BQ|T>UN_dJuKIJFQBKK0W9%uHH4~poel4@x9UaN0PL2RWksagyxx5vlHT6 z4ati1T+r{x7@>Ak=2fF5jIg6)m0CYAewUFu&7R=T6W|`ul)k)N3-W^14J(D4938Dv zBx#zt`bwW|kbM|#*VKB7QSieR1 zz6<+6t)lct$i{1+)4U6MN(trP8|@qZlL*!JE@I^s&grksysG&u(-e` zQ8FueS>RyR-b%6>Z?0}hRLvmIuXky#7RlDzW>jldrb$kT`9#S;>SF$S_)k$v)O;H? zqGDAw$6crA8f`j?Ut1rXT1#~FP=)}qXS3v4Sj`nm=3wO<=iZje!`G%#eOFoSCzVx6 z56UX#yFxn9{d&|_yZxy8>X^F^)K}|CeWijhHdt~C!D`5cwRihFtfcG}jab=Wb;hy! z_JlTa;n2Cf^r;lxt)T}`gnSxQx6>XHRQpdo6N|X|1_|fI+ zVX}RtO6gWx_Qu>7#mSQ>ksD^1sti;`apr+uomVz~+D=h-RUPwEJ+un8Y?PI#Q%#n$ z?zP8QhZTp&eQH|bEmHlIbM~)%O&x2;<{&Wl5ixA7RI?55?}|I3&Ne%C?Qlt-ouK1h z=~=9SQjZF?;Cimq+%J-w7W%)W2q)xFPOA4KPEy@?vfJk=48gvMJr_;<#7Ww{kJxK?$2)eCK8ADHTJHN zU2L$0B_#H$I_669B5AamQ^_a`3ZErfxDpShCNPLN>RPNmI)&)>bSodd#eKm7l`0N{ zgvOcE%<@U7L2@KT79B@d(6;$I0`6X>-LY+R0whfvDQ>Y%DV}r!xLszsi|HurFvYo5 zPiaf%c1P`I}NcPAf}v!nOiC&#@a(MXxzM%lhj)!tMlv-r2+;>J~& z_gX_s&e?o)w>GgSl-Y7tuTGDv4s@ajlL|hYlL6{x#t?PYdexCO>tbDi;V1U7dUjE7 z%Xvmu(V{ARP!)pBZi#NagR3?|bP~I-I^!<>*6+^4f+bh=ss@eU=pWquDWOWkC*I5O zUDfi7u5&z@>bU8$g)pb@dbe;NHP5@>?<@IP>5omHUI&@fs!dXDUK}jq0a-TnSex z&z5ivlJ~+LuIU(&IxH}zsT&8eOj*W33o}WCz}4Pj0lu~F=sgUAp_p78N-n$NveILP z{JGB4zRj@k)YweA3B&3`+3*<_UNmvjcWqXC7%i3BWc%<~N{k79eWRrZQnQc3n2J$| zTXl}0bPK`6ouP+lBTv_Hv#a(-n9aL7dZE?oea-WrG1aHJdIlXzNKp^IQ?|_Wz}l?D zpe40X0dc4;yGq-J8&#onXquP8!>bH_(tnY-H!jVj17oJwIg)iRe|I}v!w75AvLm}4 z5NA%89j$7?oQ{KM$1N2b{iUb#P2&0UYe2Q-+azoMwc<^f9~-Z+dvpqo(S&c>>5GZ4 zPG0UdysY{atkZAhW~z8;&F1K8N;ji&XlzU4vPi>iC}3$0pL(}e9yC^Yw|=fXTR&GG z>*wMFU~k8=b4@>LEW4DCtDr08*$O&+rlUJnNLK`PXl-Uab^X}QP!y|OjhsyUHauB0 zbz~_^$U!;itlYi0O6jp7(1GEDE$a}7QJ?57jKJoekn*t0O~UeS7SHj;P*GK)&~xs9 z+DOlNZ1Hewh-T=eZ!rva@gQrNee5uwLR~$^EzPAu06nM$G@x2)@n}a6O3jzt(H%V;FU_Q`9WMs^uvgx1&QqSPH7Sp^CM7*s_ty-kGwt8}Smm#L!_{)xL+nZe#AZpLz%cK#lO1CehuOiLn+7~sFq{(TF zVB;Q7e=0;I1R{)dcX2{0FSkTBUGr={M;e>foU_xQEf0TxqdS4e7o<4W z+rN1GH@=J!R3+V~1-A;R>~hs#aBOl2rMTma3tVdA(T5pMH5$4`^@S={B)P6b$#F?8 z)MyO8I{!VwtJ0(`55A6@)N!OFWlz%1ajp?txz3#iHpk&qQ`zk|UTGtnygD2DeFoS0 z494w}npZ9_yjiiW6kju`-EXRUao8zLgZEz@w<=Vj=t`xU2~&J-yqLwoSE*4)ikgtj zXM-7F)Z^ZLLxUHyEV+bf_+iY;uRCtDf?BtW@8dv{mwF9uSBa&blWURNWWSp;+$Q^S zY!_MK-AneVYB7}mc0}2ck95s=O|kj)l`;5lvH6ny!jdhxpyl|f`XbkEKgG8yzr0jA zkCx-zDw6VS6-jxlB6%5*dfnIKypkW)Y_yLnAt~+I5|T-qYG4#BYMOH6%*f9njKn56 z?z<%S$xyE63v#KBab%x5tl-&!Mb*PcIUk--&dO5R3j<;CTT{vgiN*M zY!78v=++dS6G!a$i94(rN!zb?T`SSVnAncXOi3TQ-2bN71}OFdKA81V0^9i6vpbwds^hll6|9Xt z?*x?hf<9qE!igiB`~H}YG{==HR(km51RsQA6Jl3`m+TYbI~qQN`6H*%jVFwx?oFcHV74| zn5B{JHLpuq+t(4LG3Y)BL9ftG6t~sPpc02xhb~v+x+7R;35}>vq2)rG3XWy}6l;Aq zm6o)trQotPk7)Pct974yN4AqRZ@_2SFRql6pF_9pP(cYy@d~neB}LNoa2;Ws+gVe8 z^^t43 znH8QolI!CJgY_@N=Pj39q_`#F+fE7`$?=ofe5h?xid!_Fs}BNli(8C_!TPrrYYd_- zbVs_hwp;n0*58QYy>+d}ATM%kF=RT}h~I)lUA@GbN4sNn&QiXb_257%!K7u~i)nT{ z&iuO79#m$3wXNBX>^WGlV9%@Vq=Oa;f2bs=gf5T_GfTZ_&9c!Ot``2tAAo+V7J~NT|)U!GqTXE`E@+K>M za%?3x@F*k_j?|nxqK}0dV_Kz76z<})$jEu2Chf1QOiRC+M$# zL_uKo!-U;YU`*3p#$`J+p;pwcht|Vot8stPa8#e^Y3SQFiKa|?HIZy%-}54kHwx1a zth$cO7Ymm6$RuiN-LJ;%D67h91gl^(G@p9EFd{abNVSu92%B-bf1Wjxe4j> z5?@)>5d15VFNw)%&gQ|JipeAt`reYe%s#f6lP<26?qVCS&mpYhZYagP(G@YC0*hc( z>LUwXYL>ev=MrTYd|>CwWG@=Pl zQPFE|P5yMRUCq5c^X=D7`q7n^D}qI$PsVa|xv8v&7My7qGK(xJ$cf|Hv&lX9l!enC zFzNBU(3k4z(agMCLQ<|05_gULxBvaepLO(K^8ffvn27&8e-^Fao8$eLa{fjAVfk5t zN@V6057M1=o$|6>)?H)S^zh&B7}X#D^v9on_aFcF&wu(qKYN6_$iMvgQp(58Uw;$7 z|5iW!uYdXXzyI@}{@)+})#Lxx(9`=vpKlMne(=!KJ428E?ppTNS^8T8$iI924bzs( zvi*Ih!S6rbrsw5K_|CxVQKi22{oM5U7tP;Rqq!RYpxMny?X~iCNY=loj_vvvy`G}D zK-wm(|8*lPyUy1moA)D|PmFAQ{K)Fs`Sr;5{mA+gBWu6N$o8v|&3reqobvaFmeVgV zv|}e<53Rl!T76<@<(HVXRbLOSzZlvRFEsxmFLb;yYxBj>p4bKX7uf}UenaIwj(5Kp z?-TkYzpqa)WG~$J()eOXPKkPrFP}=~qrlfceEy_P(Q>5{-PAt0E!#2pe8BjV%paa$ zhNFGXFXlv)90X>XR36zMzETS!1ppEsnS8wcL#0!CmlU)9=ij`c^Xq~6nC&~8aeUP` zzPI}GF+H)x-!l&{zHis^c;l0daV4PF%XfX*(k*UC$&m%(VJ5+ z+MIL0ceQW6g+KIz>&C;?*3nw;UG1B1;h&CIuwTyGEwT4^FISD?Dz~pC{KMb<$NxWn N{uh-$GRH8;007@Ov+Mu> literal 0 HcmV?d00001 diff --git a/applets/diskquota/CMakeLists.txt b/applets/diskquota/CMakeLists.txt new file mode 100644 index 00000000..b382fbca --- /dev/null +++ b/applets/diskquota/CMakeLists.txt @@ -0,0 +1,23 @@ + +####################################################################################### +# Package +plasma_install_package(package org.kde.plasma.diskquota) + + +####################################################################################### +# Notes Library +add_definitions(-DTRANSLATION_DOMAIN="plasma_applet_org.kde.plasma.diskquota") + +ecm_add_qml_module(diskquotaplugin URI org.kde.plasma.private.diskquota) +target_sources(diskquotaplugin PRIVATE + plugin/plugin.cpp + plugin/DiskQuota.cpp + plugin/QuotaListModel.cpp + plugin/QuotaItem.cpp +) +target_link_libraries(diskquotaplugin PRIVATE + Qt::Quick + KF6::CoreAddons + KF6::I18n +) +ecm_finalize_qml_module(diskquotaplugin) diff --git a/applets/diskquota/Messages.sh b/applets/diskquota/Messages.sh new file mode 100644 index 00000000..7e94d9f8 --- /dev/null +++ b/applets/diskquota/Messages.sh @@ -0,0 +1,2 @@ +#! /usr/bin/env bash +$XGETTEXT `find . -name \*.qml -o -name \*.cpp` -o $podir/plasma_applet_org.kde.plasma.diskquota.pot diff --git a/applets/diskquota/package/contents/ui/ListDelegateItem.qml b/applets/diskquota/package/contents/ui/ListDelegateItem.qml new file mode 100644 index 00000000..292e81cd --- /dev/null +++ b/applets/diskquota/package/contents/ui/ListDelegateItem.qml @@ -0,0 +1,88 @@ +/* + * SPDX-FileCopyrightText: 2015 Dominik Haumann + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ +import QtQuick 2.1 +import QtQuick.Layouts 1.1 + +import org.kde.kirigami 2.20 as Kirigami +import org.kde.plasma.plasmoid 2.0 +import org.kde.plasma.components 3.0 as PlasmaComponents3 + +import org.kde.plasma.private.diskquota 1.0 + +PlasmaComponents3.ItemDelegate { + id: quotaItem + property string mountPoint + property string details + property string iconName + property string usedString + property string freeString + property int usage + + onHoveredChanged: { + if (containsMouse) { + ListView.view.currentIndex = index + } else { + ListView.view.currentIndex = -1 + } + } + + onClicked: { + diskQuota.openCleanUpTool(mountPoint); + } + + RowLayout { + id: contents + width: parent.width + spacing: Kirigami.Units.gridUnit + + Kirigami.Icon { + source: iconName + Layout.alignment: Qt.AlignTop + width: Kirigami.Units.iconSizes.medium + height: width + } + + Column { + Layout.fillWidth: true + Layout.alignment: Qt.AlignTop + + RowLayout { + width: parent.width + PlasmaComponents3.Label { + Layout.fillWidth: true + text: details + textFormat: Text.PlainText + } + PlasmaComponents3.Label { + Layout.fillWidth: true + horizontalAlignment: Text.AlignRight + text: freeString + textFormat: Text.PlainText + opacity: 0.6 + } + } + PlasmaComponents3.ProgressBar { + width: parent.width + value: usage + from: 0 + to: 100 + // HACK to make progressbar clickable + MouseArea { + anchors.fill: parent + onClicked: { + quotaItem.clicked() + } + } + } + PlasmaComponents3.Label { + anchors.left: parent.left + text: usedString + textFormat: Text.PlainText + opacity: 0.6 + } + } + } +} diff --git a/applets/diskquota/package/contents/ui/main.qml b/applets/diskquota/package/contents/ui/main.qml new file mode 100644 index 00000000..62da9bd5 --- /dev/null +++ b/applets/diskquota/package/contents/ui/main.qml @@ -0,0 +1,105 @@ +/* + * SPDX-FileCopyrightText: 2015 Dominik Haumann + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ +import QtQuick 2.1 +import QtQuick.Layouts 1.1 + +import org.kde.plasma.core as PlasmaCore +import org.kde.kirigami 2.20 as Kirigami +import org.kde.plasma.plasmoid 2.0 +import org.kde.plasma.components 3.0 as PlasmaComponents3 +import org.kde.plasma.extras 2.0 as PlasmaExtras + +import org.kde.plasma.private.diskquota 1.0 + +PlasmoidItem { + id: quotaApplet + + Layout.minimumWidth: Kirigami.Units.gridUnit * 10 + Layout.minimumHeight: Kirigami.Units.gridUnit * 2 + + Plasmoid.status: { + switch (diskQuota.status) { + case DiskQuota.NeedsAttentionStatus: + return PlasmaCore.Types.NeedsAttentionStatus + case DiskQuota.ActiveStatus: + return PlasmaCore.Types.ActiveStatus + } + // default case: DiskQuota.PassiveStatus + return PlasmaCore.Types.PassiveStatus + } + + switchWidth: Kirigami.Units.gridUnit * 10 + switchHeight: Kirigami.Units.gridUnit * 10 + + Plasmoid.icon: diskQuota.iconName + toolTipMainText: diskQuota.toolTip + toolTipSubText: diskQuota.subToolTip + + Component.onCompleted: plasmoid.removeInternalAction("configure") + + DiskQuota { + id: diskQuota + } + + fullRepresentation: Item { + id: root + + width: Kirigami.Units.gridUnit * 20 + height: Kirigami.Units.gridUnit * 14 + + // HACK: connection to reset currentIndex to -1. Without this, when + // uninstalling filelight, the selection highlight remains fixed (which is wrong) + Connections { + target: diskQuota + function onCleanUpToolInstalledChanged() { + if (!diskQuota.cleanUpToolInstalled) { + listView.currentIndex = -1 + } + } + } + + Loader { + id: emptyHint + + anchors.centerIn: parent + width: parent.width - Kirigami.Units.gridUnit * 4 + + active: !diskQuota.quotaInstalled || listView.count == 0 + visible: active + asynchronous: true + + sourceComponent: PlasmaExtras.PlaceholderMessage { + width: parent.width + iconName: diskQuota.quotaInstalled ? "edit-none" : "disk-quota" + text: diskQuota.quotaInstalled ? i18nc("@info:status", "No quota restrictions found") : i18nc("@info:status", "Quota tool not found") + explanation: diskQuota.quotaInstalled ? "" : i18nc("@info:usagetip", "Please install 'quota'") + } + } + + PlasmaComponents3.ScrollView { + anchors.fill: parent + ListView { + id: listView + model: diskQuota.model + boundsBehavior: Flickable.StopAtBounds + highlight: PlasmaExtras.Highlight { } + highlightMoveDuration: 0 + highlightResizeDuration: 0 + currentIndex: -1 + delegate: ListDelegateItem { + enabled: diskQuota.cleanUpToolInstalled + width: listView.width + mountPoint: model.mountPoint + details: model.details + iconName: model.icon + usedString: model.used + freeString: model.free + usage: model.usage + } + } + } + } +} diff --git a/applets/diskquota/package/metadata.json b/applets/diskquota/package/metadata.json new file mode 100644 index 00000000..39cb499b --- /dev/null +++ b/applets/diskquota/package/metadata.json @@ -0,0 +1,147 @@ +{ + "KPackageStructure": "Plasma/Applet", + "KPlugin": { + "Authors": [ + { + "Email": "dhaumann@kde.org", + "Name": "Dominik Haumann", + "Name[ar]": "دومينيك هاومان", + "Name[az]": "Dominik Haumann", + "Name[bg]": "Dominik Haumann", + "Name[ca@valencia]": "Dominik Haumann", + "Name[ca]": "Dominik Haumann", + "Name[cs]": "Dominik Haumann", + "Name[da]": "Dominik Haumann", + "Name[de]": "Dominik Haumann", + "Name[en_GB]": "Dominik Haumann", + "Name[eo]": "Dominik Haumann", + "Name[es]": "Dominik Haumann", + "Name[eu]": "Dominik Haumann", + "Name[fi]": "Dominik Haumann", + "Name[fr]": "Dominik Haumann", + "Name[gl]": "Dominik Haumann", + "Name[he]": "דומיניק האומאן", + "Name[hu]": "Dominik Haumann", + "Name[ia]": "Dominik Haumann", + "Name[id]": "Dominik Haumann", + "Name[is]": "Dominik Haumann", + "Name[it]": "Dominik Haumann", + "Name[ja]": "Dominik Haumann", + "Name[ka]": "Dominik Haumann", + "Name[ko]": "Dominik Haumann", + "Name[lt]": "Dominik Haumann", + "Name[lv]": "Dominik Haumann", + "Name[nl]": "Dominik Haumann", + "Name[nn]": "Dominik Haumann", + "Name[pl]": "Dominik Haumann", + "Name[pt]": "Dominik Haumann", + "Name[pt_BR]": "Dominik Haumann", + "Name[ro]": "Dominik Haumann", + "Name[ru]": "Dominik Haumann", + "Name[sk]": "Dominik Haumann", + "Name[sl]": "Dominik Haumann", + "Name[sv]": "Dominik Haumann", + "Name[tr]": "Dominik Haumann", + "Name[uk]": "Dominik Haumann", + "Name[vi]": "Dominik Haumann", + "Name[x-test]": "xxDominik Haumannxx", + "Name[zh_CN]": "Dominik Haumann", + "Name[zh_TW]": "Dominik Haumann" + } + ], + "BugReportUrl": "https://bugs.kde.org/enter_bug.cgi?product=kdeplasma-addons&component=Disk Quota", + "Category": "Utilities", + "Description": "Shows details about disk quota", + "Description[ar]": "تظهر تفاصيل حول حصة القرص", + "Description[az]": "Disk kvotası təfərrüatlarını göstərir", + "Description[bg]": "Показва подробности за дискова квота", + "Description[ca@valencia]": "Mostra els detalls de la quota de disc", + "Description[ca]": "Mostra els detalls de la quota de disc", + "Description[da]": "Vis detaljer om diskkvoter", + "Description[de]": "Zeigt Details zum Speicherplatzkontingent an", + "Description[en_GB]": "Shows details about disk quota", + "Description[eo]": "Montras detalojn pri diskkvoto", + "Description[es]": "Muestra detalles sobre la cuota de disco", + "Description[eu]": "Diskoaren kuotari buruzko zehaztasunak erakusten ditu", + "Description[fi]": "Näyttää levytilarajoitustietoa", + "Description[fr]": "Affiche des informations détaillées concernant le quota de disques", + "Description[gl]": "Amosa información sobre a cota de disco", + "Description[he]": "הצגת פרטים על מִכְסַת הכוננים", + "Description[hu]": "A lemezkvóta részleteinek megjelenítése", + "Description[ia]": "Monstra detalios super quota de disco", + "Description[id]": "Menampilkan detail tentang quota disk", + "Description[is]": "Birta ítarlegar upplýsingar um kvóta á diskplássi", + "Description[it]": "Mostra i dettagli relativi alle quote disco", + "Description[ja]": "ディスククォータの詳細を表示します", + "Description[ka]": "დისკების კვოტის შესახებ ინფორმაციის ჩვენება", + "Description[ko]": "디스크 할당량 정보 표시", + "Description[lt]": "Išsami informacija apie leidžiamą disko duomenų kiekį", + "Description[lv]": "Parāda informāciju par diska kvotas izmantojumu", + "Description[nl]": "Toont details van schijfquota", + "Description[nn]": "Vis informasjon om diskkvotar", + "Description[pl]": "Pokazuje szczegóły o przydziale dyskowym", + "Description[pt]": "Mostra detalhes sobre a quota do disco", + "Description[pt_BR]": "Mostra detalhes sobre a quota do disco", + "Description[ro]": "Arată detalii despre cota de disc", + "Description[ru]": "Просмотр дисковых квот", + "Description[sk]": "Zobrazí podrobnosti o diskovej kvóte", + "Description[sl]": "Prikaži podrobnosti diskovne kvote", + "Description[sv]": "Visar information om diskkvot", + "Description[tr]": "Disk kotası hakkında ayrıntılar göster", + "Description[uk]": "Показати дані щодо квот на диску", + "Description[vi]": "Hiện các chi tiết về hạn ngạch đĩa", + "Description[x-test]": "xxShows details about disk quotaxx", + "Description[zh_CN]": "显示磁盘配额详情", + "Description[zh_TW]": "顯示磁碟配額詳細資訊", + "EnabledByDefault": false, + "Icon": "disk-quota", + "Id": "org.kde.plasma.diskquota", + "License": "LGPL", + "Name": "Disk Quota", + "Name[ar]": "حصّة القرص", + "Name[az]": "Disk kvotası", + "Name[bg]": "Дискова квота", + "Name[ca@valencia]": "Quota de disc", + "Name[ca]": "Quota de disc", + "Name[cs]": "Kvóta disku", + "Name[da]": "Diskkvote", + "Name[de]": "Speicherplatzkontingent", + "Name[en_GB]": "Disk Quota", + "Name[eo]": "Diska Kvoto", + "Name[es]": "Cuota de disco", + "Name[eu]": "Disko-kuota", + "Name[fi]": "Levytilarajoitus", + "Name[fr]": "Quota de disque", + "Name[gl]": "Cota de disco", + "Name[he]": "מִכְסַת כונן", + "Name[hu]": "Lemezkvóta", + "Name[ia]": "Quota de disco", + "Name[id]": "Quota Disk", + "Name[is]": "Diskkvóti", + "Name[it]": "Quota disco", + "Name[ja]": "ディスククォータ", + "Name[ka]": "დისკის კვოტა", + "Name[ko]": "디스크 할당량", + "Name[lt]": "Leidžiamas disko duomenų kiekis", + "Name[lv]": "Diska kvota", + "Name[nl]": "Schijfquota", + "Name[nn]": "Disk­kvote", + "Name[pl]": "Przydział dyskowy", + "Name[pt]": "Quota do Disco", + "Name[pt_BR]": "Quota de disco", + "Name[ro]": "Cotă de disc", + "Name[ru]": "Дисковые квоты", + "Name[sk]": "Kvóty disku", + "Name[sl]": "Diskovna kvota", + "Name[sv]": "Diskkvot", + "Name[tr]": "Disk Kotası", + "Name[uk]": "Квота на диску", + "Name[vi]": "Hạn ngạch đĩa", + "Name[x-test]": "xxDisk Quotaxx", + "Name[zh_CN]": "磁盘配额", + "Name[zh_TW]": "磁碟配額", + "Website": "https://kde.org/plasma-desktop/" + }, + "X-Plasma-API-Minimum-Version": "6.0", + "X-Plasma-NotificationAreaCategory": "SystemServices" +} diff --git a/applets/diskquota/plugin/DiskQuota.cpp b/applets/diskquota/plugin/DiskQuota.cpp new file mode 100644 index 00000000..575889c9 --- /dev/null +++ b/applets/diskquota/plugin/DiskQuota.cpp @@ -0,0 +1,272 @@ +/* + * SPDX-FileCopyrightText: 2015 Dominik Haumann + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ +#include "DiskQuota.h" +#include "QuotaItem.h" + +#include +#include + +#include +#include +#include +// #include + +DiskQuota::DiskQuota(QObject *parent) + : QObject(parent) + , m_timer(new QTimer(this)) + , m_quotaProcess(new QProcess(this)) + , m_model(new QuotaListModel(this)) +{ + connect(m_timer, &QTimer::timeout, this, &DiskQuota::updateQuota); + m_timer->start(2 * 60 * 1000); // check every 2 minutes + + connect(m_quotaProcess, (void (QProcess::*)(int, QProcess::ExitStatus)) & QProcess::finished, this, &DiskQuota::quotaProcessFinished); + + updateQuota(); +} + +bool DiskQuota::quotaInstalled() const +{ + return m_quotaInstalled; +} + +void DiskQuota::setQuotaInstalled(bool installed) +{ + if (m_quotaInstalled != installed) { + m_quotaInstalled = installed; + + if (!installed) { + m_model->clear(); + setStatus(PassiveStatus); + setToolTip(i18n("Disk Quota")); + setSubToolTip(i18n("Please install 'quota'")); + } + + Q_EMIT quotaInstalledChanged(); + } +} + +bool DiskQuota::cleanUpToolInstalled() const +{ + return m_cleanUpToolInstalled; +} + +void DiskQuota::setCleanUpToolInstalled(bool installed) +{ + if (m_cleanUpToolInstalled != installed) { + m_cleanUpToolInstalled = installed; + Q_EMIT cleanUpToolInstalledChanged(); + } +} + +DiskQuota::TrayStatus DiskQuota::status() const +{ + return m_status; +} + +void DiskQuota::setStatus(TrayStatus status) +{ + if (m_status != status) { + m_status = status; + Q_EMIT statusChanged(); + } +} + +QString DiskQuota::iconName() const +{ + return m_iconName; +} + +void DiskQuota::setIconName(const QString &name) +{ + if (m_iconName != name) { + m_iconName = name; + Q_EMIT iconNameChanged(); + } +} + +QString DiskQuota::toolTip() const +{ + return m_toolTip; +} + +void DiskQuota::setToolTip(const QString &toolTip) +{ + if (m_toolTip != toolTip) { + m_toolTip = toolTip; + Q_EMIT toolTipChanged(); + } +} + +QString DiskQuota::subToolTip() const +{ + return m_subToolTip; +} + +void DiskQuota::setSubToolTip(const QString &subToolTip) +{ + if (m_subToolTip != subToolTip) { + m_subToolTip = subToolTip; + Q_EMIT subToolTipChanged(); + } +} + +static QString iconNameForQuota(int quota) +{ + if (quota < 50) { + return QStringLiteral("disk-quota"); + } else if (quota < 75) { + return QStringLiteral("disk-quota-low"); + } else if (quota < 90) { + return QStringLiteral("disk-quota-high"); + } + + // quota >= 90% + return QStringLiteral("disk-quota-critical"); +} + +void DiskQuota::updateQuota() +{ + const bool quotaFound = !QStandardPaths::findExecutable(QStringLiteral("quota")).isEmpty(); + setQuotaInstalled(quotaFound); + if (!quotaFound) { + return; + } + + // for now, only filelight is supported + setCleanUpToolInstalled(!QStandardPaths::findExecutable(QStringLiteral("filelight")).isEmpty()); + + // kill running process in case it hanged for whatever reason + if (m_quotaProcess->state() != QProcess::NotRunning) { + m_quotaProcess->kill(); + } + + // Try to run 'quota' + const QStringList args{ + QStringLiteral("--show-mntpoint"), // second entry is e.g. '/home' + QStringLiteral("--hide-device"), // hide e.g. /dev/sda3 + QStringLiteral("--no-mixed-pathnames"), // trim leading slashes from NFSv4 mountpoints + QStringLiteral("--all-nfs"), // show all mount points + QStringLiteral("--no-wrap"), // do not wrap long lines + QStringLiteral("--quiet-refuse"), // no not print error message when NFS server does not respond + }; + + m_quotaProcess->start(QStringLiteral("quota"), args, QIODevice::ReadOnly); +} + +void DiskQuota::quotaProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + Q_UNUSED(exitCode) + + if (exitStatus != QProcess::NormalExit) { + m_model->clear(); + setToolTip(i18n("Disk Quota")); + setSubToolTip(i18n("Running quota failed")); + return; + } + + // get quota output + const QString rawData = QString::fromLocal8Bit(m_quotaProcess->readAllStandardOutput()); + // qDebug() << rawData; + + const QStringList lines = rawData.split(QRegularExpression(QStringLiteral("[\r\n]")), Qt::SkipEmptyParts); + // Testing + // QStringList lines = QStringList() + // << QStringLiteral("/home/peterpan 3975379* 5000000 7000000 57602 0 0") + // << QStringLiteral("/home/archive 2263536 6000000 5100000 3932 0 0") + // << QStringLiteral("/home/shared 4271196* 10000000 7000000 57602 0 0"); + // << QStringLiteral("/home/peterpan %1* 5000000 7000000 57602 0 0").arg(qrand() % 5000000) + // << QStringLiteral("/home/archive %1 5000000 5100000 3932 0 0").arg(qrand() % 5000000) + // << QStringLiteral("/home/shared %1* 5000000 7000000 57602 0 0").arg(qrand() % 5000000); + // lines.removeAt(qrand() % lines.size()); + + // format class needed for GiB/MiB/KiB formatting + KFormat fmt; + int maxQuota = 0; + QList items; + + // assumption: Filesystem starts with slash + for (const QString &line : lines) { + if (!line.contains(QLatin1Char('/'))) { + continue; + } + + QStringList parts = line.split(QLatin1Char(' '), Qt::SkipEmptyParts); + // valid lines range from 7 to 9 parts (grace not always there): + // Disk quotas for user dh (uid 1000): + // Filesystem blocks quota limit grace files quota limit grace + // /home 16296500 50000000 60000000 389155 0 0 + // /home 16296500* 50000000 60000000 6 389155 0 0 + // /home 16296500* 50000000 60000000 4 389155 0 0 5 + // ^...........we want these...........^ + // NOTE: In case of a soft limit violation, a '*' is added in the used blocks. + // Hence, the star is removed below, if applicable + + if (parts.size() < 4) { + continue; + } + + // 'quota' uses kilo bytes -> factor 1024 + // NOTE: int is not large enough, hence qint64 + const qint64 used = parts[1].remove(QLatin1Char('*')).toLongLong() * 1024; + qint64 softLimit = parts[2].toLongLong() * 1024; + const qint64 hardLimit = parts[3].toLongLong() * 1024; + if (softLimit == 0) { // softLimit might be unused (0) + softLimit = hardLimit; + } + const qint64 freeSize = softLimit - used; + const int percent = qMin(100, qMax(0, qRound(used * 100.0 / softLimit))); + + QuotaItem item; + item.setIconName(iconNameForQuota(percent)); + item.setMountPoint(parts[0]); + item.setUsage(percent); + item.setMountString(i18nc("usage of quota, e.g.: '/home/bla: 38% used'", "%1: %2% used", parts[0], percent)); + item.setUsedString(i18nc("e.g.: 12 GiB of 20 GiB", "%1 of %2", fmt.formatByteSize(used), fmt.formatByteSize(softLimit))); + item.setFreeString(i18nc("e.g.: 8 GiB free", "%1 free", fmt.formatByteSize(qMax(qint64(0), freeSize)))); + + items.append(item); + + maxQuota = qMax(maxQuota, percent); + } + + // qDebug() << "QUOTAS:" << quotas; + + // make sure max quota is 100. Could be more, due to the + // hard limit > soft limit, and we take soft limit as 100% + maxQuota = qMin(100, maxQuota); + + // update icon in panel + setIconName(iconNameForQuota(maxQuota)); + + // update status + setStatus(maxQuota < 50 ? PassiveStatus : maxQuota < 98 ? ActiveStatus : NeedsAttentionStatus); + + if (!items.isEmpty()) { + setToolTip(i18nc("example: Quota: 83% used", "Quota: %1% used", maxQuota)); + setSubToolTip(QString()); + } else { + setToolTip(i18n("Disk Quota")); + setSubToolTip(i18n("No quota restrictions found.")); + } + + // merge new items, add new ones, remove old ones + m_model->updateItems(items); +} + +QuotaListModel *DiskQuota::model() const +{ + return m_model; +} + +void DiskQuota::openCleanUpTool(const QString &mountPoint) +{ + if (!cleanUpToolInstalled()) { + return; + } + + QProcess::startDetached(QStringLiteral("filelight"), {mountPoint}); +} diff --git a/applets/diskquota/plugin/DiskQuota.h b/applets/diskquota/plugin/DiskQuota.h new file mode 100644 index 00000000..5585458c --- /dev/null +++ b/applets/diskquota/plugin/DiskQuota.h @@ -0,0 +1,109 @@ +/* + * SPDX-FileCopyrightText: 2015 Dominik Haumann + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ +#ifndef PLASMA_DISK_QUOTA_H +#define PLASMA_DISK_QUOTA_H + +#include "QuotaListModel.h" +#include +#include +class QTimer; + +/** + * Class monitoring the file system quota. + * The monitoring is performed through a timer, running the 'quota' + * command line tool. + */ +class DiskQuota : public QObject +{ + Q_OBJECT + + Q_PROPERTY(bool quotaInstalled READ quotaInstalled NOTIFY quotaInstalledChanged) + Q_PROPERTY(bool cleanUpToolInstalled READ cleanUpToolInstalled NOTIFY cleanUpToolInstalledChanged) + + Q_PROPERTY(TrayStatus status READ status NOTIFY statusChanged) + Q_PROPERTY(QString toolTip READ toolTip NOTIFY toolTipChanged) + Q_PROPERTY(QString subToolTip READ subToolTip NOTIFY subToolTipChanged) + Q_PROPERTY(QString iconName READ iconName NOTIFY iconNameChanged) + + Q_PROPERTY(QuotaListModel *model READ model CONSTANT) + +public: + explicit DiskQuota(QObject *parent = nullptr); + +public: + /** + * System tray icon states. + */ + enum TrayStatus { + ActiveStatus = 0, + PassiveStatus, + NeedsAttentionStatus, + }; + Q_ENUM(TrayStatus) + +public: + bool quotaInstalled() const; + void setQuotaInstalled(bool installed); + + bool cleanUpToolInstalled() const; + void setCleanUpToolInstalled(bool installed); + + TrayStatus status() const; + void setStatus(TrayStatus status); + + QString toolTip() const; + void setToolTip(const QString &toolTip); + + QString subToolTip() const; + void setSubToolTip(const QString &subToolTip); + + QString iconName() const; + void setIconName(const QString &name); + + /** + * Getter function for the model that is used in QML. + */ + QuotaListModel *model() const; + +public Q_SLOTS: + /** + * Called every timer timeout to update the data model. + * Launches an asynchronous 'quota' process to obtain data, + * and finally calls quotaProcessFinished(). + */ + void updateQuota(); + + /** + * Processes the quota data from the 'quota' process. + */ + void quotaProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); + + /** + * Opens the cleanup tool (filelight) at the folder @p mountPoint. + */ + void openCleanUpTool(const QString &mountPoint); + +Q_SIGNALS: + void quotaInstalledChanged(); + void cleanUpToolInstalledChanged(); + void statusChanged(); + void toolTipChanged(); + void subToolTipChanged(); + void iconNameChanged(); + +private: + QTimer *m_timer = nullptr; + QProcess *m_quotaProcess = nullptr; + bool m_quotaInstalled = true; + bool m_cleanUpToolInstalled = true; + TrayStatus m_status = PassiveStatus; + QString m_iconName = QStringLiteral("disk-quota"); + QString m_toolTip; + QString m_subToolTip; + QuotaListModel *m_model = nullptr; +}; + +#endif // PLASMA_DISK_QUOTA_H diff --git a/applets/diskquota/plugin/QuotaItem.cpp b/applets/diskquota/plugin/QuotaItem.cpp new file mode 100644 index 00000000..fb3604e3 --- /dev/null +++ b/applets/diskquota/plugin/QuotaItem.cpp @@ -0,0 +1,94 @@ +/* + * SPDX-FileCopyrightText: 2015 Dominik Haumann + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ +#include "QuotaItem.h" + +#include + +QuotaItem::QuotaItem() + : m_iconName(QStringLiteral("quota")) + , m_mountPoint() + , m_usage(0) + , m_mountString() + , m_usedString() +{ +} + +QString QuotaItem::iconName() const +{ + return m_iconName; +} + +void QuotaItem::setIconName(const QString &name) +{ + m_iconName = name; +} + +QString QuotaItem::mountPoint() const +{ + return m_mountPoint; +} + +void QuotaItem::setMountPoint(const QString &mountPoint) +{ + m_mountPoint = mountPoint; +} + +int QuotaItem::usage() const +{ + return m_usage; +} + +void QuotaItem::setUsage(int usage) +{ + m_usage = usage; +} + +QString QuotaItem::mountString() const +{ + return m_mountString; +} + +void QuotaItem::setMountString(const QString &mountString) +{ + m_mountString = mountString; +} + +QString QuotaItem::usedString() const +{ + return m_usedString; +} + +void QuotaItem::setUsedString(const QString &usedString) +{ + m_usedString = usedString; +} + +QString QuotaItem::freeString() const +{ + return m_freeString; +} + +void QuotaItem::setFreeString(const QString &freeString) +{ + m_freeString = freeString; +} + +bool QuotaItem::operator==(const QuotaItem &other) const +{ + // clang-format off + return m_mountPoint == other.m_mountPoint + && m_iconName == other.m_iconName + && m_usage == other.m_usage + && m_mountString == other.m_mountString + && m_usedString == other.m_usedString + && m_freeString == other.m_freeString; + // clang-format on +} + +bool QuotaItem::operator!=(const QuotaItem &other) const +{ + return !(*this == other); +} diff --git a/applets/diskquota/plugin/QuotaItem.h b/applets/diskquota/plugin/QuotaItem.h new file mode 100644 index 00000000..9bdf4be9 --- /dev/null +++ b/applets/diskquota/plugin/QuotaItem.h @@ -0,0 +1,52 @@ +/* + * SPDX-FileCopyrightText: 2015 Dominik Haumann + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ +#ifndef PLASMA_QUOTA_ITEM_H +#define PLASMA_QUOTA_ITEM_H + +#include +#include + +/** + * Class that holds all quota info for one mount point. + */ +class QuotaItem +{ +public: + QuotaItem(); + + QString mountPoint() const; + void setMountPoint(const QString &mountPoint); + + int usage() const; + void setUsage(int usage); + + QString iconName() const; + void setIconName(const QString &name); + + QString mountString() const; + void setMountString(const QString &mountString); + + QString usedString() const; + void setUsedString(const QString &usedString); + + QString freeString() const; + void setFreeString(const QString &freeString); + + bool operator==(const QuotaItem &other) const; + bool operator!=(const QuotaItem &other) const; + +private: + QString m_iconName; + QString m_mountPoint; + int m_usage; + QString m_mountString; + QString m_usedString; + QString m_freeString; +}; + +Q_DECLARE_METATYPE(QuotaItem) + +#endif // PLASMA_QUOTA_ITEM_H diff --git a/applets/diskquota/plugin/QuotaListModel.cpp b/applets/diskquota/plugin/QuotaListModel.cpp new file mode 100644 index 00000000..46b3cb1a --- /dev/null +++ b/applets/diskquota/plugin/QuotaListModel.cpp @@ -0,0 +1,183 @@ +/* + * SPDX-FileCopyrightText: 2015 Dominik Haumann + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ +#include "QuotaListModel.h" + +#include + +QuotaListModel::QuotaListModel(QObject *parent) + : QAbstractListModel(parent) +{ +} + +namespace +{ +/** + * QML data roles. + */ +enum { + DetailsRole = Qt::UserRole, + IconRole, + FreeStringRole, + UsedStringRole, + MountPointRole, + UsageRole, +}; +} + +QHash QuotaListModel::roleNames() const +{ + QHash roles; + roles[DetailsRole] = "details"; + roles[IconRole] = "icon"; + roles[FreeStringRole] = "free"; + roles[UsedStringRole] = "used"; + roles[MountPointRole] = "mountPoint"; + roles[UsageRole] = "usage"; + + return roles; +} + +QVariant QuotaListModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= m_items.size()) { + return QVariant(); + } + + const auto item = m_items[index.row()]; + + switch (role) { + case DetailsRole: + return item.mountString(); + case IconRole: + return item.iconName(); + case FreeStringRole: + return item.freeString(); + case UsedStringRole: + return item.usedString(); + case MountPointRole: + return item.mountPoint(); + case UsageRole: + return item.usage(); + } + + return QVariant(); +} + +int QuotaListModel::rowCount(const QModelIndex &index) const +{ + if (!index.isValid()) { + return m_items.size(); + } + + return 0; +} + +bool QuotaListModel::setData(const QModelIndex &index, const QVariant &variant, int role) +{ + Q_UNUSED(role) + + const int row = index.row(); + if (index.isValid() && row < m_items.size()) { + const QuotaItem item = variant.value(); + + // This assert makes sure that changing items modify the correct item: + // therefore, the unique identifier 'mountPoint()' is used. If that + // is not the case, the newly inserted row must have an empty mountPoint(). + Q_ASSERT(item.mountPoint() == m_items[row].mountPoint() || m_items[row].mountPoint().isEmpty()); + + if (m_items[row] != item) { + m_items[row] = item; + Q_EMIT dataChanged(index, index); + return true; + } + } + + return false; +} + +bool QuotaListModel::insertRows(int row, int count, const QModelIndex &parent) +{ + // only top-level items are supported + if (parent.isValid()) { + return false; + } + + beginInsertRows(QModelIndex(), row, row + count - 1); + m_items.insert(row, count, QuotaItem()); + endInsertRows(); + + return true; +} + +bool QuotaListModel::removeRows(int row, int count, const QModelIndex &parent) +{ + // only top-level items are valid + if (parent.isValid() || (row + count) >= m_items.size()) { + return false; + } + + beginRemoveRows(QModelIndex(), row, row + count - 1); + m_items.remove(row, count); + endRemoveRows(); + + return true; +} + +void QuotaListModel::clear() +{ + beginResetModel(); + m_items.clear(); + endResetModel(); +} + +namespace +{ +QStringList mountPoints(const QList &items) +{ + QStringList list; + for (auto &item : items) { + list.append(item.mountPoint()); + } + return list; +} + +int indexOfMountPoint(const QString &mountPoint, const QList &items) +{ + for (int i = 0; i < items.size(); ++i) { + if (mountPoint == items[i].mountPoint()) { + return i; + } + } + return -1; +} +} + +void QuotaListModel::updateItems(const QList &items) +{ + QStringList unusedMountPoints = mountPoints(m_items); + + // merge existing and new mount points + for (auto &item : items) { + // remove still used item from unused list + unusedMountPoints.removeOne(item.mountPoint()); + + // insert or modify m_items + int row = indexOfMountPoint(item.mountPoint(), m_items); + if (row < 0) { + // new item: append on end + row = m_items.size(); + insertRow(row); + } + setData(createIndex(row, 0), QVariant::fromValue(item)); + } + + // remove mount points, that do not exist anymore + for (const auto &mountPoint : unusedMountPoints) { + const int row = indexOfMountPoint(mountPoint, m_items); + Q_ASSERT(row >= 0); + removeRow(row); + } +} diff --git a/applets/diskquota/plugin/QuotaListModel.h b/applets/diskquota/plugin/QuotaListModel.h new file mode 100644 index 00000000..61ac3607 --- /dev/null +++ b/applets/diskquota/plugin/QuotaListModel.h @@ -0,0 +1,71 @@ +/* + * SPDX-FileCopyrightText: 2015 Dominik Haumann + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ +#ifndef PLASMA_QUOTA_LIST_MODEL_H +#define PLASMA_QUOTA_LIST_MODEL_H + +#include +#include + +#include "QuotaItem.h" + +/** + * Data model holding disk quota items. + */ +class QuotaListModel : public QAbstractListModel +{ + Q_OBJECT + +public: + explicit QuotaListModel(QObject *parent = nullptr); + +public: // QAbstractListModel overrides + /** + * List of available roles for the QML ListView. + */ + QHash roleNames() const override; + + /** + * Returns the data for @p index and given @p role. + */ + QVariant data(const QModelIndex &index, int role) const override; + + /** + * Returns the number of items for the toplevel model index, otherwise 0. + */ + int rowCount(const QModelIndex &index) const override; + + /** + * Changes the data for @p index to @p variant. + */ + bool setData(const QModelIndex &index, const QVariant &variant, int role = Qt::EditRole) override; + + /** + * Inserts @p count rows at position @p row. + */ + bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; + + /** + * Removes @p count rows at position @p row. + */ + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; + +public: // additional helper functions + /** + * Merges @p items into the existing quota item list. Old items that are + * not available in @p items anymore are deleted. + */ + void updateItems(const QList &items); + + /** + * Clears all items in the model. + */ + void clear(); + +private: + QList m_items; +}; + +#endif // PLASMA_QUOTA_LIST_MODEL_H diff --git a/applets/diskquota/plugin/plugin.cpp b/applets/diskquota/plugin/plugin.cpp new file mode 100644 index 00000000..b9c28f6c --- /dev/null +++ b/applets/diskquota/plugin/plugin.cpp @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: 2015 Dominik Haumann + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ +#include "DiskQuota.h" +#include "QuotaListModel.h" + +#include +#include + +class DiskQuotaPlugin : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") + +public: + void registerTypes(const char *uri) override + { + qmlRegisterType(uri, 1, 0, "DiskQuota"); + qmlRegisterType(uri, 1, 0, "QuotaListModel"); + } +}; + +#include "plugin.moc" diff --git a/applets/fifteenPuzzle/CMakeLists.txt b/applets/fifteenPuzzle/CMakeLists.txt new file mode 100644 index 00000000..0d7a2c28 --- /dev/null +++ b/applets/fifteenPuzzle/CMakeLists.txt @@ -0,0 +1,14 @@ +ecm_install_icons( + ICONS sc-apps-fifteenpuzzle.svgz + DESTINATION ${KDE_INSTALL_ICONDIR} +) + +plasma_install_package(package org.kde.plasma.fifteenpuzzle) + +ecm_add_qml_module(fifteenpuzzleplugin URI org.kde.plasma.private.fifteenpuzzle) +target_sources(fifteenpuzzleplugin PRIVATE + plugin/fifteenimageprovider.cpp + plugin/fifteenpuzzleplugin.cpp +) +target_link_libraries(fifteenpuzzleplugin PRIVATE Qt::Quick Qt::Qml) +ecm_finalize_qml_module(fifteenpuzzleplugin) diff --git a/applets/fifteenPuzzle/Messages.sh b/applets/fifteenPuzzle/Messages.sh new file mode 100755 index 00000000..2fa33d76 --- /dev/null +++ b/applets/fifteenPuzzle/Messages.sh @@ -0,0 +1,4 @@ +#! /usr/bin/env bash +$EXTRACTRC `find . -name \*.rc -o -name \*.ui -o -name \*.kcfg` >> rc.cpp +$XGETTEXT `find . -name \*.qml -o -name \*.cpp` -o $podir/plasma_applet_org.kde.plasma.fifteenpuzzle.pot +rm -f rc.cpp diff --git a/applets/fifteenPuzzle/package/contents/config/config.qml b/applets/fifteenPuzzle/package/contents/config/config.qml new file mode 100644 index 00000000..552109da --- /dev/null +++ b/applets/fifteenPuzzle/package/contents/config/config.qml @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2013 Bhushan Shah + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + */ + +import QtQuick 2.15 + +import org.kde.plasma.configuration 2.0 + +ConfigModel { + ConfigCategory { + name: i18nc("@title", "Appearance") + icon: "preferences-desktop-color" + source: "configAppearance.qml" + } +} diff --git a/applets/fifteenPuzzle/package/contents/config/main.xml b/applets/fifteenPuzzle/package/contents/config/main.xml new file mode 100644 index 00000000..2bece1ee --- /dev/null +++ b/applets/fifteenPuzzle/package/contents/config/main.xml @@ -0,0 +1,29 @@ + + + + + + + + + + false + + + true + + + #333333 + + + #ffffff + + + 4 + + + + diff --git a/applets/fifteenPuzzle/package/contents/ui/FifteenPuzzle.qml b/applets/fifteenPuzzle/package/contents/ui/FifteenPuzzle.qml new file mode 100644 index 00000000..3aabc156 --- /dev/null +++ b/applets/fifteenPuzzle/package/contents/ui/FifteenPuzzle.qml @@ -0,0 +1,374 @@ +/* + * SPDX-FileCopyrightText: 2014 Jeremy Whiting + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.15 +import QtQuick.Layouts 1.15 + +import org.kde.coreaddons 1.0 as KCoreAddons +import org.kde.plasma.core as PlasmaCore +import org.kde.kirigami 2.20 as Kirigami +import org.kde.plasma.components 3.0 as PlasmaComponents3 +import org.kde.plasma.plasmoid 2.0 + +Item { + id: main + + Layout.preferredWidth: Math.max(boardSize * 50, controlsRow.width) + Layout.preferredHeight: boardSize * 50 + controlsRow.height + + readonly property int boardSize: Plasmoid.configuration.boardSize + property Component piece: Piece {} + property var pieces: [] + property int currentPosition: -1 + + property int seconds: 0 + + Keys.onPressed: { + let newPosition = main.currentPosition; + switch (event.key) { + case Qt.Key_Up: { + if (main.currentPosition < 0) { + newPosition = (main.boardSize - 1) * main.boardSize; // Start from bottom + } else if (main.currentPosition >= main.boardSize) { + newPosition = main.currentPosition - main.boardSize; + } + if (pieces[newPosition].empty) { + if (main.currentPosition < 0) { + newPosition = (main.boardSize - 2) * main.boardSize; + } else if (newPosition >= main.boardSize) { + newPosition -= main.boardSize; + } + } + break; + } + case Qt.Key_Down: { + if (main.currentPosition < 0) { + newPosition = 0; // Start from top + } else if (main.currentPosition < main.boardSize * (main.boardSize - 1)) { + newPosition = main.currentPosition + main.boardSize; + } + if (pieces[newPosition].empty) { + if (main.currentPosition < 0) { + newPosition = main.boardSize; + } else if (newPosition < main.boardSize * (main.boardSize - 1)) { + newPosition += main.boardSize; + } + } + break; + } + case Qt.Key_Left: { + if (main.currentPosition < 0) { + newPosition = main.boardSize - 1; // Start from right + } else if (main.currentPosition % main.boardSize) { + newPosition = main.currentPosition - 1; + } + if (pieces[newPosition].empty) { + if (main.currentPosition < 0) { + newPosition = main.boardSize - 2; + } else if (newPosition % main.boardSize) { + newPosition -= 1; + } + } + break; + } + case Qt.Key_Right: { + if (main.currentPosition < 0) { + newPosition = 0; // Start from left + } else if ((main.currentPosition + 1) % main.boardSize) { + newPosition = main.currentPosition + 1; + } + if (pieces[newPosition].empty) { + if (main.currentPosition < 0) { + newPosition = 1; + } else if ((newPosition + 1) % main.boardSize) { + newPosition += 1; + } + } + break; + } + default: + return; + } + + // Edge empty case: don't move + if (pieces[newPosition].empty) { + newPosition = main.currentPosition; + } + + pieces[newPosition].forceActiveFocus(); + event.accepted = true; + } + + function fillBoard() { + // Clear out old board + for (const piece of pieces) { + piece.destroy(); + } + main.currentPosition = -1; + + pieces = []; + const count = boardSize * boardSize; + if (piece.status === Component.Ready) { + for (let i = 0; i < count; ++i) { + const newPiece = piece.createObject(mainGrid, {"number": i, "position": i }); + pieces[i] = newPiece; + newPiece.activeFocusChanged.connect(() => { + if (newPiece.activeFocus) { + main.currentPosition = newPiece.position; + } + }); + newPiece.activated.connect(pieceClicked); + } + shuffleBoard(); + } + } + + function shuffleBoard() { + // Hide the solved rectangle in case it was visible + solvedRect.visible = false; + main.seconds = 0; + main.currentPosition = -1; + + const count = boardSize * boardSize; + for (let i = count - 1; i >= 0; --i) { + // choose a random number such that 0 <= rand <= i + const rand = Math.floor(Math.random() * 10) % (i + 1); + swapPieces(i, rand); + } + + // make sure the new board is solveable + + // count the number of inversions + // an inversion is a pair of tiles at positions a, b where + // a < b but value(a) > value(b) + + // also count the number of lines the blank tile is from the bottom + let inversions = 0; + let blankRow = -1; + for (let i = 0; i < count; ++i) { + if (pieces[i].empty) { + blankRow = Math.floor(i / boardSize); + continue; + } + for (let j = 0; j < i; ++j) { + if (pieces[j].empty) { + continue; + } + if (pieces[i].number < pieces[j].number) { + ++inversions; + } + } + } + + if (blankRow === -1) { + console.log("Unable to find row of blank tile"); + } + + // we have a solveable board if: + // size is odd: there are an even number of inversions + // size is even: the number of inversions is odd if and only if + // the blank tile is on an odd row from the bottom- + const sizeMod2 = Math.floor(boardSize % 2); + const inversionsMod2 = Math.floor(inversions % 2); + const solveable = (sizeMod2 === 1 && inversionsMod2 === 0) || + (sizeMod2 === 0 && (inversionsMod2 === 0) === (Math.floor((boardSize - blankRow) % 2) === 1)); + if (!solveable) { + // make the grid solveable by swapping two adjacent pieces around + let pieceA = 0; + let pieceB = 1; + if (pieces[pieceA].empty) { + pieceA = boardSize + 1; + } else if (pieces[pieceB].empty) { + pieceB = boardSize; + } + swapPieces(pieceA, pieceB); + } + secondsTimer.stop(); + } + + // recursive function: performs swap and returns true when it has found an + // empty piece in the direction given by deltas. + function swapWithEmptyPiece(position, deltaRow, deltaColumn): bool { + const row = Math.floor(position / boardSize); + const column = position % boardSize; + + const nextRow = row + deltaRow; + const nextColumn = column + deltaColumn; + const nextPosition = nextRow * boardSize + nextColumn; + + if (nextRow < 0 || nextRow >= boardSize || nextColumn < 0 || nextColumn >= boardSize) { + return false; + } + + if (pieces[nextPosition].empty || swapWithEmptyPiece(nextPosition, deltaRow, deltaColumn)) { + swapPieces(position, nextPosition); + return true; + } + + return false; + } + + function pieceClicked(position) { + // deltas: up, down, left, right + for (const [row, col] of [[-1, 0], [1, 0], [0, -1], [0, 1]]) { + // stop at first direction that has (or rather "had" at this point) the empty piece + if (swapWithEmptyPiece(position, row, col)) { + main.currentPosition += col + main.boardSize * row; + break; + } + } + secondsTimer.start(); + checkSolved(); + } + + function checkSolved() { + const count = boardSize * boardSize; + for (let i = 0; i < count - 2; ++i) { + if (pieces[i].number > pieces[i + 1].number) { + // Not solved. + return; + } + } + solved(); + } + + function solved() { + // Show a message that it was solved. + console.log("Puzzle was solved"); + solvedRect.visible = true; + // Stop the timer + secondsTimer.stop(); + } + + function swapPieces(first, second) { + const firstPiece = pieces[first]; + const secondPiece = pieces[second]; + let temp = firstPiece.position; + firstPiece.position = secondPiece.position; + secondPiece.position = temp; + temp = pieces[first]; + pieces[first] = pieces[second]; + pieces[second] = temp; + } + + function timerText() { + return i18nc("The time since the puzzle started, in minutes and seconds", + "Time: %1", KCoreAddons.Format.formatDuration(seconds * 1000, KCoreAddons.FormatTypes.FoldHours)); + } + + Rectangle { + id: mainGrid + color: Kirigami.Theme.backgroundColor + anchors { + top: parent.top + left: parent.left + right: parent.right + bottom: controlsRow.top + bottomMargin: Kirigami.Units.smallSpacing + } + + activeFocusOnTab: true + + onActiveFocusChanged: { + // Move focus to the first non-empty piece + if (activeFocus) { + if (main.currentPosition < 0) { + if (main.pieces[0].empty) { + main.pieces[1].forceActiveFocus(); + } else { + main.pieces[0].forceActiveFocus(); + } + } else { + main.pieces[main.currentPosition].forceActiveFocus(); + } + } + } + } + + RowLayout { + id: controlsRow + anchors { + margins: Kirigami.Units.smallSpacing + bottom: parent.bottom + horizontalCenter: parent.horizontalCenter + } + PlasmaComponents3.Button { + id: button + Layout.fillWidth: true + icon.name: "roll" + text: i18nc("@action:button", "Shuffle"); + onClicked: main.shuffleBoard(); + } + + PlasmaComponents3.Label { + id: timeLabel + Layout.fillWidth: true + text: main.timerText() + textFormat: Text.PlainText + color: Kirigami.Theme.textColor + } + } + + Rectangle { + id: solvedRect + visible: false + anchors.fill: mainGrid + color: Kirigami.Theme.backgroundColor + z: 0 + + Image { + id: solvedImage + anchors.fill: parent + z: 1 + source: "image://fifteenpuzzle/" + boardSize + "-all-0-0-" + Plasmoid.configuration.imagePath + visible: Plasmoid.configuration.useImage + cache: false + function update() { + const tmp = source; + source = ""; + source = tmp; + } + } + + PlasmaComponents3.Label { + id: solvedLabel + anchors.centerIn: parent + color: Kirigami.Theme.textColor + text: i18nc("@info", "Solved! Try again.") + textFormat: Text.PlainText + z: 2 + } + } + + Timer { + id: secondsTimer + interval: 1000 + repeat: true + + onTriggered: ++main.seconds + } + + Connections { + target: Plasmoid.configuration + function onBoardSizeChanged() { + main.fillBoard(); + solvedImage.update(); + } + } + + Connections { + target: Plasmoid.configuration + function onImagePathChanged() { + main.fillBoard(); + solvedImage.update(); + } + } + + Component.onCompleted: { + main.fillBoard(); + solvedImage.update(); + } +} diff --git a/applets/fifteenPuzzle/package/contents/ui/Piece.qml b/applets/fifteenPuzzle/package/contents/ui/Piece.qml new file mode 100644 index 00000000..d9efcb90 --- /dev/null +++ b/applets/fifteenPuzzle/package/contents/ui/Piece.qml @@ -0,0 +1,117 @@ +/* + * SPDX-FileCopyrightText: 2014 Jeremy Whiting + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.15 +import QtQuick.Layouts 1.15 + +import org.kde.plasma.core as PlasmaCore +import org.kde.kirigami as Kirigami +import org.kde.plasma.components 3.0 as PlasmaComponents3 +import org.kde.plasma.extras 2.0 as PlasmaExtras +import org.kde.plasma.plasmoid 2.0 + +Rectangle { + id: piece + color: Plasmoid.configuration.boardColor + border.color: "black" + border.width: 1 + radius: Kirigami.Units.cornerRadius + visible: !empty + + Layout.minimumWidth: 10 + Layout.preferredWidth: 30 + + Layout.minimumHeight: 10 + Layout.preferredHeight: 30 + + x: boardColumn * (width + margin) + margin / 2 + y: boardRow * (height + margin) + margin / 2 + width: pieceWidth + height: pieceHeight + + signal activated(int position) + + readonly property int boardSize: Plasmoid.configuration.boardSize + readonly property int margin: Kirigami.Units.smallSpacing + readonly property int pieceWidth: (parent.width - (margin * boardSize)) / boardSize + readonly property int pieceHeight: (parent.height - (margin * boardSize)) / boardSize + readonly property int boardColumn: (position % boardSize) + readonly property int boardRow: Math.floor(position / boardSize) + readonly property bool empty: number === 0 + + property int number + property int position + + Keys.onPressed: { + switch (event.key) { + case Qt.Key_Space: + case Qt.Key_Enter: + case Qt.Key_Return: + case Qt.Key_Select: + piece.trigger(); + break; + } + } + Accessible.name: pieceNumeral.text + Accessible.role: Accessible.Button + + function trigger() { + piece.forceActiveFocus(); + piece.activated(position); + } + + Behavior on x { + NumberAnimation { + duration: Kirigami.Units.longDuration + easing.type: Easing.InOutQuad + } + } + Behavior on y { + NumberAnimation { + duration: Kirigami.Units.longDuration + easing.type: Easing.InOutQuad + } + } + + TapHandler { + onTapped: piece.trigger() + } + + Loader { + anchors.fill: parent + active: parent.activeFocus + asynchronous: true + z: 0 + + sourceComponent: PlasmaExtras.Highlight { + hovered: true + } + } + + PlasmaComponents3.Label { + id: pieceNumeral + anchors.centerIn: parent + text: piece.number + textFormat: Text.PlainText + color: Plasmoid.configuration.numberColor + visible: Plasmoid.configuration.showNumerals + z: 1 + } + + Loader { + anchors.fill: parent + + active: Plasmoid.configuration.useImage + asynchronous: true + z: 0 + + sourceComponent: Image { + id: pieceImage + source: "image://fifteenpuzzle/" + boardSize + "-" + number + "-" + pieceWidth + "-" + pieceHeight + "-" + Plasmoid.configuration.imagePath + cache: false + } + } +} diff --git a/applets/fifteenPuzzle/package/contents/ui/blanksquare.svg b/applets/fifteenPuzzle/package/contents/ui/blanksquare.svg new file mode 100644 index 00000000..d99c68b8 --- /dev/null +++ b/applets/fifteenPuzzle/package/contents/ui/blanksquare.svg @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/applets/fifteenPuzzle/package/contents/ui/configAppearance.qml b/applets/fifteenPuzzle/package/contents/ui/configAppearance.qml new file mode 100644 index 00000000..7db33dec --- /dev/null +++ b/applets/fifteenPuzzle/package/contents/ui/configAppearance.qml @@ -0,0 +1,140 @@ +/* + * SPDX-FileCopyrightText: 2013 Bhushan Shah + * SPDX-FileCopyrightText: 2013 Sebastian Kügler + * SPDX-FileCopyrightText: 2014 Kai Uwe Broulik + * SPDX-FileCopyrightText: 2014 Jeremy Whiting + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + */ + +import QtQuick 2.15 +import QtCore +import QtQuick.Controls 2.15 as QtControls +import QtQuick.Layouts 1.15 +import QtQuick.Dialogs as QtDialogs + +import org.kde.kquickcontrols 2.0 as KQC +import org.kde.kirigami 2.20 as Kirigami +import org.kde.kcmutils as KCM + +KCM.SimpleKCM { + property alias cfg_boardSize: sizeSpinBox.value + property alias cfg_boardColor: pieceColorPicker.color + property alias cfg_numberColor: numberColorPicker.color + property alias cfg_showNumerals: showNumeralsCheckBox.checked + + property alias cfg_useImage: imageBackgroundRadioButton.checked + property alias cfg_imagePath: imagePathTextField.text + + Kirigami.FormLayout { + + QtControls.ButtonGroup { + id: radioGroup + } + + // Need to manually set checked state for the color button based on the + // other onebecause it's not aliased to a saved property + Component.onCompleted: { + colorBackgroundRadioButton.checked = !imageBackgroundRadioButton.checked; + } + + QtControls.SpinBox { + id: sizeSpinBox + Kirigami.FormData.label: i18nc("@label:spinbox", "Grid size:") + } + + Item { + Kirigami.FormData.isSection: true + } + + // Color background + RowLayout { + Kirigami.FormData.label: i18n("Background:") + + QtControls.RadioButton { + id: colorBackgroundRadioButton + QtControls.ButtonGroup.group: radioGroup + + text: i18n("Color:") + } + + KQC.ColorButton { + id: pieceColorPicker + enabled: colorBackgroundRadioButton.checked + } + } + + // Image background + RowLayout { + QtControls.RadioButton { + id: imageBackgroundRadioButton + QtControls.ButtonGroup.group: radioGroup + + text: i18n("Image:") + } + + Kirigami.ActionTextField { + id: imagePathTextField + enabled: imageBackgroundRadioButton.checked + + Layout.fillWidth: true + placeholderText: i18nc("@info:placeholder", "Path to custom image…") + + rightActions: [ + Kirigami.Action { + icon.name: "edit-clear" + visible: imagePathTextField.text.length !== 0 + onTriggered: imagePathTextField.text = ""; + } + ] + } + + QtControls.Button { + id: imageButton + enabled: imageBackgroundRadioButton.checked + + icon.name: "document-open" + + QtControls.ToolTip { + visible: imageButton.hovered + text: i18nc("@info:tooltip", "Choose image…") + } + + onClicked: imagePicker.open() + + QtDialogs.FileDialog { + id: imagePicker + + title: i18nc("@title:window", "Choose an Image") + + currentFolder: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0] + + // TODO ask QImageReader for supported formats + nameFilters: [ i18n("Image Files (*.png *.jpg *.jpeg *.bmp *.svg *.svgz)") ] + + onAccepted: { + imagePathTextField.text = selectedFile.toString().replace("file://", "") + } + } + } + } + + Item { + Kirigami.FormData.isSection: true + } + + RowLayout { + Kirigami.FormData.label: i18n("Tiles:") + + QtControls.CheckBox { + id: showNumeralsCheckBox + text: i18n("Colored numbers:") + } + + KQC.ColorButton { + id: numberColorPicker + enabled: showNumeralsCheckBox.checked + } + } + } +} diff --git a/applets/fifteenPuzzle/package/contents/ui/main.qml b/applets/fifteenPuzzle/package/contents/ui/main.qml new file mode 100644 index 00000000..358190a1 --- /dev/null +++ b/applets/fifteenPuzzle/package/contents/ui/main.qml @@ -0,0 +1,30 @@ +/* + * SPDX-FileCopyrightText: 2013 Heena Mahour + * SPDX-FileCopyrightText: 2013 Sebastian Kügler + * SPDX-FileCopyrightText: 2014 Kai Uwe Broulik + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.15 +import QtQuick.Layouts 1.15 + +import org.kde.plasma.core as PlasmaCore +import org.kde.kirigami 2.20 as Kirigami +import org.kde.plasma.plasmoid 2.0 +// nothing used, but done to trigger imageprovider addition in plugin init +import org.kde.plasma.private.fifteenpuzzle 0.1 as Private + +PlasmoidItem { + id: root + + switchWidth: Kirigami.Units.gridUnit * 16 - 20 + switchHeight: switchWidth + + preferredRepresentation: fullRepresentation + + toolTipMainText: i18n("Fifteen Puzzle"); + toolTipSubText: i18n("Solve by arranging in order"); + + fullRepresentation: FifteenPuzzle { } +} diff --git a/applets/fifteenPuzzle/package/metadata.json b/applets/fifteenPuzzle/package/metadata.json new file mode 100644 index 00000000..38a08474 --- /dev/null +++ b/applets/fifteenPuzzle/package/metadata.json @@ -0,0 +1,146 @@ +{ + "KPackageStructure": "Plasma/Applet", + "KPlugin": { + "Authors": [ + { + "Email": "jesperht@yahoo.com", + "Name": "Jesper Thomschutz", + "Name[ar]": "يسبر تومشوتز", + "Name[az]": "Jesper Thomschutz", + "Name[bg]": "Jesper Thomschutz", + "Name[ca@valencia]": "Jesper Thomschutz", + "Name[ca]": "Jesper Thomschutz", + "Name[cs]": "Jesper Thomschutz", + "Name[da]": "Jesper Thomschutz", + "Name[de]": "Jesper Thomschutz", + "Name[en_GB]": "Jesper Thomschutz", + "Name[eo]": "Jesper Thomschutz", + "Name[es]": "Jesper Thomschutz", + "Name[eu]": "Jesper Thomschutz", + "Name[fi]": "Jesper Thomschutz", + "Name[fr]": "Jesper Thomschutz", + "Name[gl]": "Jesper Thomschutz", + "Name[he]": "ג׳ספר תומשוץ", + "Name[hu]": "Jesper Thomschutz", + "Name[ia]": "Jesper Thomschutz", + "Name[id]": "Jesper Thomschutz", + "Name[is]": "Jesper Thomschutz", + "Name[it]": "Jesper Thomschutz", + "Name[ja]": "Jesper Thomschutz", + "Name[ka]": "Jesper Thomschutz", + "Name[ko]": "Jesper Thomschutz", + "Name[lt]": "Jesper Thomschutz", + "Name[lv]": "Jesper Thomschutz", + "Name[nl]": "Jesper Thomschutz", + "Name[nn]": "Jesper Thomschutz", + "Name[pl]": "Jesper Thomschutz", + "Name[pt]": "Jesper Thomschutz", + "Name[pt_BR]": "Jesper Thomschutz", + "Name[ro]": "Jesper Thomschutz", + "Name[ru]": "Jesper Thomschutz", + "Name[sk]": "Jesper Thomschutz", + "Name[sl]": "Jesper Thomschutz", + "Name[sv]": "Jesper Thomschutz", + "Name[tr]": "Jesper Thomschutz", + "Name[uk]": "Jesper Thomschutz", + "Name[vi]": "Jesper Thomschutz", + "Name[x-test]": "xxJesper Thomschutzxx", + "Name[zh_CN]": "Jesper Thomschutz", + "Name[zh_TW]": "Jesper Thomschutz" + } + ], + "BugReportUrl": "https://bugs.kde.org/enter_bug.cgi?product=kdeplasma-addons&component=Fifteen Puzzle", + "Category": "Fun and Games", + "Description": "Put the pieces in order", + "Description[ar]": "ضع القطع في مكانها الصحيح", + "Description[az]": "Hissələri cərgə ilə düzün", + "Description[bg]": "Подредете парчетата правилно", + "Description[ca@valencia]": "Poseu les peces en ordre", + "Description[ca]": "Poseu les peces en ordre", + "Description[cs]": "Zkuste dílky správně poskládat", + "Description[da]": "Put brikkerne i rækkefølge", + "Description[de]": "Bringen Sie die Teile in die richtige Reihenfolge", + "Description[en_GB]": "Put the pieces in order", + "Description[eo]": "Meti la pecojn en ordo", + "Description[es]": "Colocar las fichas en orden", + "Description[eu]": "Piezak ordenan ipini", + "Description[fi]": "Järjestä palaset", + "Description[fr]": "Mettre les pièces dans l'ordre", + "Description[gl]": "Poña as pezas en orde", + "Description[he]": "סידור החלקים במקום", + "Description[hu]": "Tegye a darabokat a megfelelő sorrendbe", + "Description[ia]": "Pone le pecias in ordine", + "Description[id]": "Masukkan potongan-potongan dalam urutan", + "Description[is]": "Raða hlutunum í rétta röð", + "Description[it]": "Rimetti i pezzi in ordine", + "Description[ja]": "ピースを正しい順序に並べるパズル", + "Description[ka]": "ნაწილების რიგში დალაგება", + "Description[ko]": "조각을 순서대로 맞춰 보세요", + "Description[lt]": "Surinkite dėlionę", + "Description[lv]": "Salieciet gabaliņus pareizā kārtībā", + "Description[nl]": "Zet de stukken in de juiste volgorde", + "Description[nn]": "Flytt brikkene i rett rekkjefølgje", + "Description[pl]": "Puzzle do układania", + "Description[pt]": "Colocar as peças por ordem", + "Description[pt_BR]": "Coloque as peças em ordem", + "Description[ro]": "Potriviți piesele", + "Description[ru]": "Головоломка: расположите клеточки по порядку", + "Description[sk]": "Zoraďte jednotlivé časti", + "Description[sl]": "Postavi delčke v red", + "Description[sv]": "Placera brickorna i ordning", + "Description[tr]": "Parçaları sıraya koy", + "Description[uk]": "Спробуйте впорядкувати плитки", + "Description[vi]": "Đảo các ô về đúng thứ tự", + "Description[x-test]": "xxPut the pieces in orderxx", + "Description[zh_CN]": "按顺序排列板块的小游戏", + "Description[zh_TW]": "把磁磚整理整齊", + "Icon": "fifteenpuzzle", + "Id": "org.kde.plasma.fifteenpuzzle", + "License": "GPL-2.0+", + "Name": "Fifteen Puzzle", + "Name[ar]": "أحجية الخمسة عشر", + "Name[az]": "On beş tapmaca", + "Name[bg]": "Fifteen Puzzle", + "Name[ca@valencia]": "Trencaclosques quinze", + "Name[ca]": "Trencaclosques quinze", + "Name[cs]": "Hra Patnáct", + "Name[da]": "Femte gåder", + "Name[de]": "Fünfzehn Steine", + "Name[en_GB]": "Fifteen Puzzle", + "Name[eo]": "Dek kvin Puzlo", + "Name[es]": "Rompecabezas «Quince»", + "Name[eu]": "Hamabosteko buruhaustekoa", + "Name[fi]": "Viisitoista-peli", + "Name[fr]": "Puzzle à 15 pièces", + "Name[gl]": "Quebracabezas de quince pezas", + "Name[he]": "תצרף חמש־עשרה", + "Name[hu]": "15-ös kirakó", + "Name[ia]": "Dece-cinque puzzle", + "Name[id]": "Fifteen Puzzle", + "Name[is]": "Fimmtán púsl", + "Name[it]": "Il gioco del quindici", + "Name[ja]": "15 ピースパズル", + "Name[ka]": "15 პაზლი", + "Name[ko]": "열다섯 조각 퍼즐", + "Name[lt]": "Penkiolika dalių", + "Name[lv]": "Piecpadsmit puzle", + "Name[nl]": "Vijftien-puzzel", + "Name[nn]": "Spelet femten", + "Name[pl]": "Piętnaście puzzli", + "Name[pt]": "Puzzle dos Quinze", + "Name[pt_BR]": "Quebra-cabeça quinze", + "Name[ro]": "Fifteen Puzzle", + "Name[ru]": "Пятнашки", + "Name[sk]": "Hlavolam pätnástka", + "Name[sl]": "Fifteen Puzzle", + "Name[sv]": "Femtonspel", + "Name[tr]": "Onbeşli Yapboz", + "Name[uk]": "Гра у п’ятнашки", + "Name[vi]": "Trò chơi đảo ô", + "Name[x-test]": "xxFifteen Puzzlexx", + "Name[zh_CN]": "十五格拼图", + "Name[zh_TW]": "十五數字推盤遊戲", + "Website": "https://kde.org/plasma-desktop/" + }, + "X-Plasma-API-Minimum-Version": "6.0" +} diff --git a/applets/fifteenPuzzle/plugin/fifteenimageprovider.cpp b/applets/fifteenPuzzle/plugin/fifteenimageprovider.cpp new file mode 100644 index 00000000..75cd6298 --- /dev/null +++ b/applets/fifteenPuzzle/plugin/fifteenimageprovider.cpp @@ -0,0 +1,87 @@ +/* + * SPDX-FileCopyrightText: 2014 Jeremy Whiting + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + */ + +#include "fifteenimageprovider.h" + +#include + +FifteenImageProvider::FifteenImageProvider() + : QQuickImageProvider(QQuickImageProvider::Pixmap) + , m_boardSize(4) + , m_pieceWidth(30) + , m_pieceHeight(30) +{ +} + +QPixmap FifteenImageProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) +{ + Q_UNUSED(requestedSize); // wanted sizes are actually encoded in the id + + // id format is boardSize-imagenumber-pieceWidth-pieceHeight-imagePath + qDebug() << "pixmap requested with id " << id; + QStringList idParts = id.split(QLatin1Char('-')); + if (idParts.size() < 4) { + *size = QSize(); + return QPixmap(); + } + + bool update = false; + int boardSize = idParts.at(0).toInt(); + int pieceWidth = idParts.at(2).toInt(); + int pieceHeight = idParts.at(3).toInt(); + QString path = idParts.at(4); + if (path != m_imagePath && !path.isEmpty()) { + m_imagePath = path; + qDebug() << "loading pixmap from file " << path << m_pixmap.load(path); + update = true; + } + + if (idParts.at(1) == QLatin1String("all")) { + return m_pixmap; + } else { + if (pieceWidth != m_pieceWidth || pieceHeight != m_pieceHeight) { + m_pieceWidth = pieceWidth; + m_pieceHeight = pieceHeight; + update = true; + } + + if (m_boardSize != boardSize) { + m_boardSize = boardSize; + update = true; + } + + if (update) { + updatePixmaps(); + } + + int number = idParts.at(1).toInt(); + + qDebug() << "pixmap for piece " << number << " requested"; + if (number > 0 && number < m_pixmaps.size()) { + *size = QSize(m_pieceWidth, m_pieceHeight); + return m_pixmaps.at(number); + } + } + + *size = QSize(); + return QPixmap(); +} + +void FifteenImageProvider::updatePixmaps() +{ + QSize size(m_pieceWidth * m_boardSize, m_pieceHeight * m_boardSize); + QPixmap copyPixmap = m_pixmap.scaled(size); + + m_pixmaps.clear(); + m_pixmaps.resize(m_boardSize * m_boardSize); + + for (int i = 0; i < m_boardSize * m_boardSize; i++) { + int posX = (i % m_boardSize) * m_pieceWidth; + int posY = (i / m_boardSize) * m_pieceHeight; + + m_pixmaps[i] = copyPixmap.copy(posX, posY, m_pieceWidth, m_pieceHeight); + } +} diff --git a/applets/fifteenPuzzle/plugin/fifteenimageprovider.h b/applets/fifteenPuzzle/plugin/fifteenimageprovider.h new file mode 100644 index 00000000..a13de439 --- /dev/null +++ b/applets/fifteenPuzzle/plugin/fifteenimageprovider.h @@ -0,0 +1,34 @@ +/* + * SPDX-FileCopyrightText: 2014 Jeremy Whiting + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + */ + +#ifndef FIFTEENIMAGEPROVIDER_H +#define FIFTEENIMAGEPROVIDER_H + +#include +#include +#include + +class FifteenImageProvider : public QQuickImageProvider +{ +public: + FifteenImageProvider(); + + QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override; + +private: + // Update our pixmaps, called when sizes change or boardSize changes + void updatePixmaps(); + + QString m_imagePath; + QPixmap m_pixmap; + int m_boardSize; + int m_pieceWidth; + int m_pieceHeight; + + QList m_pixmaps; +}; + +#endif diff --git a/applets/fifteenPuzzle/plugin/fifteenpuzzleplugin.cpp b/applets/fifteenPuzzle/plugin/fifteenpuzzleplugin.cpp new file mode 100644 index 00000000..b53fbdcd --- /dev/null +++ b/applets/fifteenPuzzle/plugin/fifteenpuzzleplugin.cpp @@ -0,0 +1,31 @@ +/* + * SPDX-FileCopyrightText: 2014 Jeremy Whiting + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + */ + +#include "fifteenimageprovider.h" + +#include +#include +#include + +class FifteenPuzzlePlugin : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") + +public: + void registerTypes(const char *uri) override + { + // Do some dummy registration, otherwise the plugin will be ignored at runtime + qmlRegisterTypeNotAvailable(uri, 0, 1, "FifteenPuzzle", QStringLiteral("fifteenpuzzle")); + } + void initializeEngine(QQmlEngine *engine, const char *uri) override + { + qDebug() << "initializeEngine called, uri is " << uri; + engine->addImageProvider(QStringLiteral("fifteenpuzzle"), new FifteenImageProvider()); + } +}; + +#include "fifteenpuzzleplugin.moc" diff --git a/applets/fifteenPuzzle/sc-apps-fifteenpuzzle.svgz b/applets/fifteenPuzzle/sc-apps-fifteenpuzzle.svgz new file mode 100644 index 0000000000000000000000000000000000000000..357d355d9f99909f395a5295a0037cd058549393 GIT binary patch literal 1107 zcmV-Z1g!fXiwFP!000000IgO{Z{s!)z3;D3l}iFxG`~L-I{_M?MS(4P*iG+RnYPuH zC4*vOdw=~76-Tt=B%1_H4l>`&@C~0IN1j}7S9Nl4U01cuG$jb9iEYYuRyB)hdi(W{ zOs7fbi)L2TZDXft)27!~pC-R$S#slS;q5HhRDPNK)qL;DVr`T2rT6Q6Jl?5 zb&K&ul4VfY-7h{RNdmM@m&0P39u#a?pGq;tV~C|g=G(IJZ4H#q~3m{qp%fDM_) z5h4kt`ywdoe7k;3PC)Zr4RQxCE8Dtt`I#(BMZ{$fwdSXd?;@k6=7j&6jJ$9(J+Mv2$0G01uqx`O-$}Whri?M9IRkYD zbT>^67=s8d#elkLsyI@ZVr~X-{_FGhaG8pQdNe7-dLY?9tn% z@^CNiI_tiGWcHutwzDT$UtQ63^VY4V>8kLq+MZ*?2@q2`${1oy3T&v&*oYx4ln{!9 z1xCzhX|xz+gfIl@9P$lPMlgN>YQNX(Tub>5uh09x4|*NAq9H!YhTm#Gt`&aKey_^3 zCEy;SkRU|Ft#^P1OKGJHkM^J;*C3{)P%#%!qmee?HJZLGKRAv2!fEkq`X8OfC{{|C z5f1-gHcGG|qf9eIHKAZGfw)mzslBB@zc5YvPm4HV>tbged7Q7#mU{`~+*XJRY0S`j z9N{Ms2X~f2XO!Xta2MVMT7c0HSdvBX|HUkT~_Pfo`Fy<2D{W9xMnPa3lgMfI1SCzM}uHcdLM{HJf%fh*0 zmp5%=_h_U%@<)R%L^)SXM(`$pifJVd zGPy*ANiOAxB1*ZY{0M>#s8nIU2y&z(!L;8nqQIvpmm-n`OlnT4j@?8IswqQ4Dsb6| zfX6r|gd{{Fr3}=EOwOcH@qEX{2y6h<>;NUmP$=fHn+V0WKM!C$;LU&yIz)!3)-zK7 zNS@kjm`+GtMys7(_BUH?uu1e80lmy~9(&zQ!8=ka65ZtO)*oCYkWv|NwK}{>+#q4J zN3PUbVZe82>tgC+5Tw)}(;#>-|-d^^2pOZJRV^KdK zn|?S95J)q;1CC{TMI_jL4+QngVx51x^ZpR~udSNA*Y8BQhc@QLs;YPS@0EwREmq0D z))mPYIR25aK}Jf&!Mk8p=Uo`YAiA@ib@w*Cns|U3NO?i<7bTB8VFMt7_w@B-+^??S Z3G5G(c>%xP;rBK?egaXSANDp3000Ph6@>r* literal 0 HcmV?d00001 diff --git a/applets/fuzzy-clock/Messages.sh b/applets/fuzzy-clock/Messages.sh new file mode 100755 index 00000000..53ce920d --- /dev/null +++ b/applets/fuzzy-clock/Messages.sh @@ -0,0 +1,2 @@ +#! /usr/bin/env bash +$XGETTEXT `find . -name \*.qml` -o $podir/plasma_applet_org.kde.plasma.fuzzyclock.pot diff --git a/applets/fuzzy-clock/package/contents/config/config.qml b/applets/fuzzy-clock/package/contents/config/config.qml new file mode 100644 index 00000000..a65882ea --- /dev/null +++ b/applets/fuzzy-clock/package/contents/config/config.qml @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2013 Bhushan Shah + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + */ + +import QtQuick 2.0 + +import org.kde.plasma.configuration 2.0 + +ConfigModel { + ConfigCategory { + name: i18nc("@title", "Appearance") + icon: "preferences-desktop-color" + source: "configAppearance.qml" + } +} diff --git a/applets/fuzzy-clock/package/contents/config/main.xml b/applets/fuzzy-clock/package/contents/config/main.xml new file mode 100644 index 00000000..6cb3d0c7 --- /dev/null +++ b/applets/fuzzy-clock/package/contents/config/main.xml @@ -0,0 +1,20 @@ + + + + + + + 1 + + + false + + + false + + + + \ No newline at end of file diff --git a/applets/fuzzy-clock/package/contents/ui/FuzzyClock.qml b/applets/fuzzy-clock/package/contents/ui/FuzzyClock.qml new file mode 100644 index 00000000..abf28bfa --- /dev/null +++ b/applets/fuzzy-clock/package/contents/ui/FuzzyClock.qml @@ -0,0 +1,314 @@ +/* + * SPDX-FileCopyrightText: 2013 Heena Mahour + * SPDX-FileCopyrightText: 2013 Sebastian Kügler + * SPDX-FileCopyrightText: 2013 Martin Klapetek + * SPDX-FileCopyrightText: 2014 David Edmundson + * SPDX-FileCopyrightText: 2014 Kai Uwe Broulik + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.15 +import QtQuick.Layouts 1.1 + +import org.kde.plasma.core as PlasmaCore +import org.kde.kirigami 2.20 as Kirigami +import org.kde.plasma.components 3.0 as PlasmaComponents3 +import org.kde.plasma.plasmoid 2.0 + +Item { + id: main + + Layout.minimumWidth: vertical ? 0 : sizehelper.paintedWidth + (Kirigami.Units.smallSpacing * 2) + Layout.maximumWidth: vertical ? Infinity : Layout.minimumWidth + Layout.preferredWidth: vertical ? undefined : Layout.minimumWidth + + Layout.minimumHeight: vertical ? sizehelper.paintedHeight + (Kirigami.Units.smallSpacing * 2) : 0 + Layout.maximumHeight: vertical ? Layout.minimumHeight : Infinity + Layout.preferredHeight: vertical ? Layout.minimumHeight : Kirigami.Units.iconSizes.sizeForLabels * 2 + + readonly property bool vertical: plasmoid.formFactor == PlasmaCore.Types.Vertical + + readonly property int fuzzyness: plasmoid.configuration.fuzzyness + + readonly property var hourNames: [ + [ i18n("One o’clock"), + i18n("Five past one"), + i18n("Ten past one"), + i18n("Quarter past one"), + i18n("Twenty past one"), + i18n("Twenty-five past one"), + i18n("Half past one"), + i18n("Twenty-five to two"), + i18n("Twenty to two"), + i18n("Quarter to two"), + i18n("Ten to two"), + i18n("Five to two") ], + [ i18n("Two o’clock"), + i18n("Five past two"), + i18n("Ten past two"), + i18n("Quarter past two"), + i18n("Twenty past two"), + i18n("Twenty-five past two"), + i18n("Half past two"), + i18n("Twenty-five to three"), + i18n("Twenty to three"), + i18n("Quarter to three"), + i18n("Ten to three"), + i18n("Five to three") ], + [ i18n("Three o’clock"), + i18n("Five past three"), + i18n("Ten past three"), + i18n("Quarter past three"), + i18n("Twenty past three"), + i18n("Twenty-five past three"), + i18n("Half past three"), + i18n("Twenty-five to four"), + i18n("Twenty to four"), + i18n("Quarter to four"), + i18n("Ten to four"), + i18n("Five to four") ], + [ i18n("Four o’clock"), + i18n("Five past four"), + i18n("Ten past four"), + i18n("Quarter past four"), + i18n("Twenty past four"), + i18n("Twenty-five past four"), + i18n("Half past four"), + i18n("Twenty-five to five"), + i18n("Twenty to five"), + i18n("Quarter to five"), + i18n("Ten to five"), + i18n("Five to five") ], + [ i18n("Five o’clock"), + i18n("Five past five"), + i18n("Ten past five"), + i18n("Quarter past five"), + i18n("Twenty past five"), + i18n("Twenty-five past five"), + i18n("Half past five"), + i18n("Twenty-five to six"), + i18n("Twenty to six"), + i18n("Quarter to six"), + i18n("Ten to six"), + i18n("Five to six") ], + [ i18n("Six o’clock"), + i18n("Five past six"), + i18n("Ten past six"), + i18n("Quarter past six"), + i18n("Twenty past six"), + i18n("Twenty-five past six"), + i18n("Half past six"), + i18n("Twenty-five to seven"), + i18n("Twenty to seven"), + i18n("Quarter to seven"), + i18n("Ten to seven"), + i18n("Five to seven") ], + [ i18n("Seven o’clock"), + i18n("Five past seven"), + i18n("Ten past seven"), + i18n("Quarter past seven"), + i18n("Twenty past seven"), + i18n("Twenty-five past seven"), + i18n("Half past seven"), + i18n("Twenty-five to eight"), + i18n("Twenty to eight"), + i18n("Quarter to eight"), + i18n("Ten to eight"), + i18n("Five to eight") ], + [ i18n("Eight o’clock"), + i18n("Five past eight"), + i18n("Ten past eight"), + i18n("Quarter past eight"), + i18n("Twenty past eight"), + i18n("Twenty-five past eight"), + i18n("Half past eight"), + i18n("Twenty-five to nine"), + i18n("Twenty to nine"), + i18n("Quarter to nine"), + i18n("Ten to nine"), + i18n("Five to nine") ], + [ i18n("Nine o’clock"), + i18n("Five past nine"), + i18n("Ten past nine"), + i18n("Quarter past nine"), + i18n("Twenty past nine"), + i18n("Twenty-five past nine"), + i18n("Half past nine"), + i18n("Twenty-five to ten"), + i18n("Twenty to ten"), + i18n("Quarter to ten"), + i18n("Ten to ten"), + i18n("Five to ten") ], + [ i18n("Ten o’clock"), + i18n("Five past ten"), + i18n("Ten past ten"), + i18n("Quarter past ten"), + i18n("Twenty past ten"), + i18n("Twenty-five past ten"), + i18n("Half past ten"), + i18n("Twenty-five to eleven"), + i18n("Twenty to eleven"), + i18n("Quarter to eleven"), + i18n("Ten to eleven"), + i18n("Five to eleven") ], + [ i18n("Eleven o’clock"), + i18n("Five past eleven"), + i18n("Ten past eleven"), + i18n("Quarter past eleven"), + i18n("Twenty past eleven"), + i18n("Twenty-five past eleven"), + i18n("Half past eleven"), + i18n("Twenty-five to twelve"), + i18n("Twenty to twelve"), + i18n("Quarter to twelve"), + i18n("Ten to twelve"), + i18n("Five to twelve") ], + [ i18n("Twelve o’clock"), + i18n("Five past twelve"), + i18n("Ten past twelve"), + i18n("Quarter past twelve"), + i18n("Twenty past twelve"), + i18n("Twenty-five past twelve"), + i18n("Half past twelve"), + i18n("Twenty-five to one"), + i18n("Twenty to one"), + i18n("Quarter to one"), + i18n("Ten to one"), + i18n("Five to one") ] + ] + + readonly property var halflingTime: [ + i18n("Sleep"), i18n("Breakfast"), i18n("Second Breakfast"), i18n("Elevenses"), + i18n("Lunch"), i18n("Afternoon tea"), i18n("Dinner"), i18n("Supper") + ] + + readonly property var dayTime: [ + i18n("Night"), i18n("Early morning"), i18n("Morning"), i18n("Almost noon"), + i18n("Noon"), i18n("Afternoon"), i18n("Evening"), i18n("Late evening") + ] + + readonly property var weekTime: [ + i18n("Start of week"), i18n("Middle of week"), i18n("End of week"), i18n("Weekend!") + ] + + function timeString() { + var d = new Date(dataSource.data["Local"]["DateTime"]) + var hours = d.getHours() + var minutes = d.getMinutes() + + if (main.fuzzyness == 1 || main.fuzzyness == 2) { + var sector = 0 + var realHour = 0 + + if (main.fuzzyness == 1) { + if (minutes > 2) { + sector = (minutes - 3) / 5 + 1 + } + } else { + // this formula has been determined by carefully filling a spreadsheet + // and looking at the numbers :) + sector = ((minutes + 7) / 15 * 3) + // now round down to the nearest three + sector = (Math.floor(sector / 3) * 3) + } + + if (hours % 12 > 0) { + realHour = hours % 12 - 1 + } else { + realHour = 12 - (hours % 12 + 1) + } + + sector = Math.floor(sector); + if (sector == 12) { + realHour += 1 + if (Math.floor(realHour) >= hourNames.length) { + realHour = 0 + } + sector = 0 + } + + return hourNames[Math.floor(realHour)][sector] + } else if (main.fuzzyness == 3) { + return halflingTime[Math.floor(hours / 3)] + } else if (main.fuzzyness == 4) { + return dayTime[Math.floor(hours / 3)] + } else { + var dow = d.getDay() + + var weekTimeId + if (dow == 1) { + weekTimeId = 0 + } else if (dow >= 2 && dow <= 4) { + weekTimeId = 1 + } else if (dow == 5) { + weekTimeId = 2 + } else { + weekTimeId = 3 + } + + return weekTime[weekTimeId] + } + } + + activeFocusOnTab: true + + Accessible.name: Plasmoid.title + Accessible.description: timeLabel.text + Accessible.role: Accessible.Button + + PlasmaComponents3.Label { + id: timeLabel + font { + weight: plasmoid.configuration.boldText ? Font.Bold : Font.Normal + italic: plasmoid.configuration.italicText + pixelSize: 1024 + } + minimumPixelSize: Kirigami.Units.iconSizes.sizeForLabels + fontSizeMode: Text.Fit + text: timeString() + + wrapMode: Text.NoWrap + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + height: 0 + width: 0 + anchors { + fill: parent + leftMargin: Kirigami.Units.smallSpacing + rightMargin: Kirigami.Units.smallSpacing + } + } + + property bool wasExpanded: false + + readonly property alias mouseArea: mouseArea + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + onPressed: wasExpanded = root.expanded + onClicked: root.expanded = !wasExpanded + } + + Text { + id: sizehelper + font.weight: timeLabel.font.weight + font.italic: timeLabel.font.italic + font.pixelSize: vertical ? Kirigami.Units.gridUnit * 2 : 1024 // random "big enough" size - this is used as a max pixelSize by the fontSizeMode + minimumPixelSize: Math.round(Kirigami.Units.gridUnit / 2) + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + text: timeLabel.text + fontSizeMode: vertical ? Text.HorizontalFit : Text.VerticalFit + + wrapMode: Text.NoWrap + visible: false + anchors { + fill: parent + leftMargin: Kirigami.Units.smallSpacing + rightMargin: Kirigami.Units.smallSpacing + } + } +} diff --git a/applets/fuzzy-clock/package/contents/ui/configAppearance.qml b/applets/fuzzy-clock/package/contents/ui/configAppearance.qml new file mode 100644 index 00000000..e907e17e --- /dev/null +++ b/applets/fuzzy-clock/package/contents/ui/configAppearance.qml @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2013 Bhushan Shah + * SPDX-FileCopyrightText: 2013 Sebastian Kügler + * SPDX-FileCopyrightText: 2014 Kai Uwe Broulik + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + */ + +import QtQuick 2.0 +import QtQuick.Controls 2.5 as QQC2 +import QtQuick.Layouts 1.0 +import org.kde.kirigami 2.5 as Kirigami +import org.kde.kcmutils as KCM + +KCM.SimpleKCM { + property alias cfg_boldText: boldCheckBox.checked + property alias cfg_italicText: italicCheckBox.checked + + property alias cfg_fuzzyness: fuzzyness.value + + Kirigami.FormLayout { + QQC2.CheckBox { + id: boldCheckBox + Kirigami.FormData.label: i18nc("@title:group", "Font:") + text: i18nc("@option:check", "Bold text") + } + + QQC2.CheckBox { + id: italicCheckBox + text: i18nc("@option:check", "Italic text") + } + + Item { + Kirigami.FormData.isSection: true + } + + QQC2.Slider { + id: fuzzyness + Kirigami.FormData.label: i18nc("@title:group", "Fuzzyness:") + from: 1 + to: 5 + stepSize: 1 + } + + RowLayout { + Layout.fillWidth: true + + QQC2.Label { + text: i18nc("@item:inrange", "Accurate") + textFormat: Text.PlainText + } + + Item { + Layout.fillWidth: true + } + + QQC2.Label { + text: i18nc("@item:inrange", "Fuzzy") + textFormat: Text.PlainText + } + } + } +} diff --git a/applets/fuzzy-clock/package/contents/ui/main.qml b/applets/fuzzy-clock/package/contents/ui/main.qml new file mode 100644 index 00000000..a37ea5d2 --- /dev/null +++ b/applets/fuzzy-clock/package/contents/ui/main.qml @@ -0,0 +1,54 @@ +/* + * SPDX-FileCopyrightText: 2013 Heena Mahour + * SPDX-FileCopyrightText: 2013 Sebastian Kügler + * SPDX-FileCopyrightText: 2014 Kai Uwe Broulik + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +import QtQml +import QtQuick 2.0 +import QtQuick.Layouts 1.1 + +import org.kde.plasma.plasmoid 2.0 +import org.kde.plasma.core as PlasmaCore +import org.kde.kirigami 2.20 as Kirigami +import org.kde.plasma.plasma5support 2.0 as P5Support +import org.kde.plasma.extras 2.0 as PlasmaExtras +import org.kde.plasma.workspace.calendar 2.0 as PlasmaCalendar + +PlasmoidItem { + id: root + + readonly property date currentDateTime: dataSource.data.Local ? dataSource.data.Local.DateTime : new Date() + + width: Kirigami.Units.gridUnit * 10 + height: Kirigami.Units.gridUnit * 4 + + preferredRepresentation: compactRepresentation + + // keep this consistent with toolTipMainText and toolTipSubText in analog-clock + toolTipMainText: Qt.locale().toString(currentDateTime, "dddd") + toolTipSubText: Qt.locale().toString(currentDateTime, Qt.locale().timeFormat(Locale.LongFormat)) + "\n" + Qt.locale().toString(currentDateTime, Qt.locale().dateFormat(Locale.LongFormat).replace(/(^dddd.?\s)|(,?\sdddd$)/, "")) + + Plasmoid.backgroundHints: PlasmaCore.Types.ShadowBackground | PlasmaCore.Types.ConfigurableBackground + + P5Support.DataSource { + id: dataSource + engine: "time" + connectedSources: ["Local"] + interval: root.compactRepresentationItem.mouseArea.containsMouse ? 1000 : 60000 + intervalAlignment: root.compactRepresentationItem.mouseArea.containsMouse ? P5Support.Types.NoAlignment : P5Support.Types.AlignToMinute + } + + compactRepresentation: FuzzyClock { } + + fullRepresentation: PlasmaCalendar.MonthView { + Layout.minimumWidth: Kirigami.Units.gridUnit * 20 + Layout.minimumHeight: Kirigami.Units.gridUnit * 20 + + Kirigami.Theme.inherit: false + Kirigami.Theme.colorSet: Kirigami.Theme.Window + + today: currentDateTime + } +} diff --git a/applets/fuzzy-clock/package/metadata.json b/applets/fuzzy-clock/package/metadata.json new file mode 100644 index 00000000..527bead9 --- /dev/null +++ b/applets/fuzzy-clock/package/metadata.json @@ -0,0 +1,150 @@ +{ + "KPackageStructure": "Plasma/Applet", + "KPlugin": { + "Authors": [ + { + "Email": "kde@privat.broulik.de", + "Name": "Kai Uwe Broulik", + "Name[ar]": "كاي أووي بروتيك", + "Name[az]": "Kai Uwe Broulik", + "Name[bg]": "Kai Uwe Broulik", + "Name[ca@valencia]": "Kai Uwe Broulik", + "Name[ca]": "Kai Uwe Broulik", + "Name[cs]": "Kai Uwe Broulik", + "Name[da]": "Kai Uwe Broulik", + "Name[de]": "Kai Uwe Broulik", + "Name[en_GB]": "Kai Uwe Broulik", + "Name[eo]": "Kai Uwe Broulik", + "Name[es]": "Kai Uwe Broulik", + "Name[eu]": "Kai Uwe Broulik", + "Name[fi]": "Kai Uwe Broulik", + "Name[fr]": "Kai Uwe Broulik", + "Name[gl]": "Kai Uwe Broulik", + "Name[he]": "קאי אווה ברוליק", + "Name[hu]": "Kai Uwe Broulik", + "Name[ia]": "Kai Uwe Broulik", + "Name[id]": "Kai Uwe Broulik", + "Name[is]": "Kai Uwe Broulik", + "Name[it]": "Kai Uwe Broulik", + "Name[ja]": "Kai Uwe Broulik", + "Name[ka]": "კაი უვე ბროულიკი", + "Name[ko]": "Kai Uwe Broulik", + "Name[lt]": "Kai Uwe Broulik", + "Name[lv]": "Kai Uwe Broulik", + "Name[nl]": "Kai Uwe Broulik", + "Name[nn]": "Kai Uwe Broulik", + "Name[pl]": "Kai Uwe Broulik", + "Name[pt]": "Kai Uwe Broulik", + "Name[pt_BR]": "Kai Uwe Broulik", + "Name[ro]": "Kai Uwe Broulik", + "Name[ru]": "Kai Uwe Broulik", + "Name[sk]": "Kai Uwe Broulik", + "Name[sl]": "Kai Uwe Broulik", + "Name[sv]": "Kai Uwe Broulik", + "Name[tr]": "Kai Uwe Broulik", + "Name[uk]": "Kai Uwe Broulik", + "Name[vi]": "Kai Uwe Broulik", + "Name[x-test]": "xxKai Uwe Broulikxx", + "Name[zh_CN]": "Kai Uwe Broulik", + "Name[zh_TW]": "Kai Uwe Broulik" + } + ], + "BugReportUrl": "https://bugs.kde.org/enter_bug.cgi?product=kdeplasma-addons&component=fuzzy-clock", + "Category": "Date and Time", + "Description": "Time displayed in a less precise format", + "Description[ar]": "الوقت يظهر بتنسيق أقل دقة", + "Description[az]": "Təxmini vaxt sözlə göstərilir", + "Description[bg]": "Показване на ориентировъчен час", + "Description[ca@valencia]": "Mostra l'hora en un format aproximat", + "Description[ca]": "Mostra l'hora en un format aproximat", + "Description[cs]": "Zobrazení času v nejasném formátu", + "Description[da]": "Tid vist i et mindre præcist format", + "Description[de]": "Zeigt die Zeit weniger genau an", + "Description[en_GB]": "Time displayed in a less precise format", + "Description[eo]": "Tempo montrita en malpli preciza formato", + "Description[es]": "Hora mostrada en un formato menos preciso", + "Description[eu]": "Ordua zehaztasun txikiagoko formatu batean bistaratua", + "Description[fi]": "Vähemmän tarkasti esitetty kellonaika", + "Description[fr]": "Heure affichée dans un format moins précis", + "Description[gl]": "Mostra a hora nun formato impreciso", + "Description[he]": "הצגת הזמן בצורה פחות מדויקת", + "Description[hu]": "Idő megjelenítése kevésbé pontos formátumban", + "Description[ia]": "Tempore monstrate in un formato minus precise", + "Description[id]": "Waktu ditampilkan dalam format yang kurang tepat", + "Description[is]": "Birtir tímann á minna nákvæmu sniði", + "Description[it]": "Ora mostrata in modo meno preciso", + "Description[ja]": "時間をあいまいな形式で表示します", + "Description[ka]": "დროის ნაკლებად ზუსტ ფორმატში ჩვენება", + "Description[ko]": "개략적인 형태로 시간 보여주기", + "Description[lt]": "Mažesnio tikslumo formatu rodomas laikas", + "Description[lv]": "Mazāk precīzā formātā parādīts laiks", + "Description[nl]": "De tijd in iets minder nauwkeurig formaat", + "Description[nn]": "Klokka – meir eller mindre nøyaktig", + "Description[pl]": "Wyświetla czas w mniej precyzyjnym formacie", + "Description[pt]": "A hora apresentada num formato menos exacto", + "Description[pt_BR]": "Hora exibida em formato menos preciso", + "Description[ro]": "Ora afișată într-un format mai puțin exact", + "Description[ru]": "Время в словесной записи", + "Description[sk]": "Čas zobrazený v menej presnom formáte", + "Description[sl]": "Čas prikazan v manj natančnem formatu", + "Description[sv]": "Tidvisning med ett mindre exakt format", + "Description[tr]": "Daha az kesin bir biçimde görüntülenen zaman", + "Description[uk]": "Час, показаний у менш точному форматі", + "Description[vi]": "Thời gian hiển thị ở dạng ít chính xác hơn", + "Description[x-test]": "xxTime displayed in a less precise formatxx", + "Description[zh_CN]": "用粗略格式显示时间", + "Description[zh_TW]": "用比較不精確的方式顯示時間", + "Icon": "preferences-system-time", + "Id": "org.kde.plasma.fuzzyclock", + "License": "GPL-2.0+", + "Name": "Fuzzy Clock", + "Name[ar]": "ساعة تقريبية", + "Name[az]": "Təxmini saat", + "Name[bg]": "Ориентировъчен часовник", + "Name[ca@valencia]": "Rellotge aproximat", + "Name[ca]": "Rellotge aproximat", + "Name[cs]": "Nejasné hodiny", + "Name[da]": "Fuzzy ur", + "Name[de]": "Umgangssprachliche Uhr", + "Name[en_GB]": "Fuzzy Clock", + "Name[eo]": "Neklara Horloĝo", + "Name[es]": "Reloj impreciso", + "Name[eu]": "Ordulari lausoa", + "Name[fi]": "Sumea kello", + "Name[fr]": "Horloge floue", + "Name[gl]": "Reloxo impreciso", + "Name[he]": "שעון מטורלל", + "Name[hu]": "Fuzzy óra", + "Name[ia]": "Horologio confuse", + "Name[id]": "Jam Kabur", + "Name[is]": "Ónákvæm klukka", + "Name[it]": "Orologio confuso", + "Name[ja]": "あいまい時計", + "Name[ka]": "ბუნდოვანი საათი", + "Name[ko]": "퍼지 시계", + "Name[lt]": "Netikslus laikrodis", + "Name[lv]": "Aptuvenais pulkstenis", + "Name[nl]": "Vage klok", + "Name[nn]": "Uklar klokke", + "Name[pl]": "Rozmyty zegar", + "Name[pt]": "Relógio Difuso", + "Name[pt_BR]": "Relógio aproximado", + "Name[ro]": "Ceas evaziv", + "Name[ru]": "Неточное время", + "Name[sk]": "Približné hodiny", + "Name[sl]": "Fuzzy Clock", + "Name[sv]": "Inexakt klocka", + "Name[tr]": "Belirsiz Saat", + "Name[uk]": "Нечіткий годинник", + "Name[vi]": "Đồng mơ hồ", + "Name[x-test]": "xxFuzzy Clockxx", + "Name[zh_CN]": "粗略时钟", + "Name[zh_TW]": "模糊時鐘", + "Website": "https://kde.org/plasma-desktop" + }, + "X-Plasma-API-Minimum-Version": "6.0", + "X-Plasma-Provides": [ + "org.kde.plasma.time", + "org.kde.plasma.date" + ] +} diff --git a/applets/grouping/CMakeLists.txt b/applets/grouping/CMakeLists.txt new file mode 100644 index 00000000..ae891e1e --- /dev/null +++ b/applets/grouping/CMakeLists.txt @@ -0,0 +1,16 @@ +add_definitions(-DTRANSLATION_DOMAIN=\"plasma_applet_org.kde.plasma.private.grouping\") + +plasma_install_package(package org.kde.plasma.private.grouping) +kcoreaddons_add_plugin(org.kde.plasma.private.grouping SOURCES groupingcontainment.cpp INSTALL_NAMESPACE "plasma/applets") +target_link_libraries(org.kde.plasma.private.grouping + Qt::Gui + Qt::Quick + Qt::DBus + Qt::Widgets + Plasma::Plasma + Plasma::Plasma5Support + KF6::XmlGui + KF6::I18n +) + +add_subdirectory(container) diff --git a/applets/grouping/Messages.sh b/applets/grouping/Messages.sh new file mode 100755 index 00000000..54b0507d --- /dev/null +++ b/applets/grouping/Messages.sh @@ -0,0 +1,4 @@ +#! /usr/bin/env bash +$EXTRACTRC `find . -name \*.rc -o -name \*.ui -o -name \*.kcfg | grep -v '/tests/'` >> rc.cpp +$XGETTEXT `find . -name \*.js -o -name \*.qml -o -name \*.cpp | grep -v '/tests/'` -o $podir/plasma_applet_org.kde.plasma.private.grouping.pot +rm -f rc.cpp diff --git a/applets/grouping/container/CMakeLists.txt b/applets/grouping/container/CMakeLists.txt new file mode 100644 index 00000000..e2769264 --- /dev/null +++ b/applets/grouping/container/CMakeLists.txt @@ -0,0 +1,23 @@ +plasma_install_package(package org.kde.plasma.grouping) + +set(groupedappletscontainer_SRCS + groupedappletscontainer.cpp +) +ecm_qt_declare_logging_category(groupedappletscontainer_SRCS + HEADER debug.h + IDENTIFIER GROUPING_DEBUG + CATEGORY_NAME kde.grouping + DEFAULT_SEVERITY Info + DESCRIPTION "Applet Grouping Container" + EXPORT KDEPLASMAADDONS +) + +kcoreaddons_add_plugin(org.kde.plasma.grouping SOURCES ${groupedappletscontainer_SRCS} INSTALL_NAMESPACE "plasma/applets") +target_link_libraries(org.kde.plasma.grouping + Qt::Gui + Qt::Quick + Plasma::Plasma + Plasma::PlasmaQuick + KF6::XmlGui + KF6::I18n +) diff --git a/applets/grouping/container/groupedappletscontainer.cpp b/applets/grouping/container/groupedappletscontainer.cpp new file mode 100644 index 00000000..8e0508fa --- /dev/null +++ b/applets/grouping/container/groupedappletscontainer.cpp @@ -0,0 +1,128 @@ +/* + * SPDX-FileCopyrightText: 2015 Marco Martin + * SPDX-FileCopyrightText: 2016 David Edmundson + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "groupedappletscontainer.h" +#include "debug.h" + +#include + +#include +#include +#include +#include + +GroupedAppletsContainer::GroupedAppletsContainer(QObject *parent, const KPluginMetaData &data, const QVariantList &args) + : Plasma::Applet(parent, data, args) +{ +} + +GroupedAppletsContainer::~GroupedAppletsContainer() +{ + if (destroyed()) { + m_innerContainment->destroy(); + } +} + +void GroupedAppletsContainer::init() +{ + Applet::init(); + + // in the first creation we immediately create the systray: so it's accessible during desktop scripting + uint id = config().readEntry("ContainmentId", 0); + + if (id == 0) { + ensureSystrayExists(); + } +} + +void GroupedAppletsContainer::ensureSystrayExists() +{ + if (m_innerContainment) { + return; + } + + Plasma::Containment *cont = containment(); + if (!cont) { + return; + } + + Plasma::Corona *c = cont->corona(); + if (!c) { + return; + } + + uint id = config().readEntry("ContainmentId", 0); + if (id > 0) { + const auto containments = c->containments(); + for (Plasma::Containment *candidate : containments) { + if (candidate->id() == id) { + m_innerContainment = candidate; + break; + } + } + qCDebug(GROUPING_DEBUG) << "Containment id" << id << "that used to be a grouped containment that was deleted"; + // id = 0; + } + + if (!m_innerContainment) { + m_innerContainment = c->createContainment(QStringLiteral("org.kde.plasma.private.grouping"), // + QVariantList() << QStringLiteral("org.kde.plasma:force-create")); + config().writeEntry("ContainmentId", m_innerContainment->id()); + } + + if (!m_innerContainment) { + return; + } + + m_innerContainment->setParent(this); + connect(containment(), &Plasma::Containment::screenChanged, m_innerContainment.data(), &Plasma::Containment::reactToScreenChange); + m_innerContainment->setFormFactor(formFactor()); + m_innerContainment->setLocation(location()); + + m_internalContainmentItem = PlasmaQuick::AppletQuickItem::itemForApplet(m_innerContainment); + Q_EMIT internalContainmentItemChanged(); + + setInternalAction(QStringLiteral("configure"), m_innerContainment->internalAction(QStringLiteral("configure"))); + connect(m_innerContainment.data(), &Plasma::Containment::configureRequested, this, [this](Plasma::Applet *applet) { + Q_EMIT containment()->configureRequested(applet); + }); + + if (m_internalContainmentItem) { + // don't let internal systray manage context menus + m_internalContainmentItem->setAcceptedMouseButtons(Qt::NoButton); + } + + // replace internal remove action with ours + m_innerContainment->setInternalAction(QStringLiteral("remove"), internalAction(QStringLiteral("remove"))); +} + +void GroupedAppletsContainer::constraintsEvent(Plasma::Applet::Constraints constraints) +{ + if (constraints & Plasma::Applet::LocationConstraint) { + if (m_innerContainment) { + m_innerContainment->setLocation(location()); + } + } + if (constraints & Plasma::Applet::FormFactorConstraint) { + if (m_innerContainment) { + m_innerContainment->setFormFactor(formFactor()); + } + } + + if (constraints & Plasma::Applet::UiReadyConstraint) { + ensureSystrayExists(); + } +} + +QQuickItem *GroupedAppletsContainer::internalContainmentItem() +{ + return m_internalContainmentItem; +} + +K_PLUGIN_CLASS(GroupedAppletsContainer) + +#include "groupedappletscontainer.moc" diff --git a/applets/grouping/container/groupedappletscontainer.h b/applets/grouping/container/groupedappletscontainer.h new file mode 100644 index 00000000..8db339f4 --- /dev/null +++ b/applets/grouping/container/groupedappletscontainer.h @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: 2015 Marco Martin + * SPDX-FileCopyrightText: 2016 David Edmundson + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef GROUPEDAPPLETSCONTAINER_H +#define GROUPEDAPPLETSCONTAINER_H + +#include +#include + +class GroupedAppletsContainer : public Plasma::Applet +{ + Q_OBJECT + Q_PROPERTY(QQuickItem *internalContainmentItem READ internalContainmentItem NOTIFY internalContainmentItemChanged) + +public: + explicit GroupedAppletsContainer(QObject *parent, const KPluginMetaData &data, const QVariantList &args); + ~GroupedAppletsContainer() override; + + void init() override; + + QQuickItem *internalContainmentItem(); + +protected: + void constraintsEvent(Plasma::Applet::Constraints constraints) override; + void ensureSystrayExists(); + +Q_SIGNALS: + void internalContainmentItemChanged(); + +private: + QPointer m_innerContainment; + QPointer m_internalContainmentItem; +}; + +#endif diff --git a/applets/grouping/container/package/contents/ui/main.qml b/applets/grouping/container/package/contents/ui/main.qml new file mode 100644 index 00000000..f82ee8df --- /dev/null +++ b/applets/grouping/container/package/contents/ui/main.qml @@ -0,0 +1,43 @@ +/* + * SPDX-FileCopyrightText: 2016 Marco Martin + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +import QtQuick 2.1 +import QtQuick.Layouts 1.1 +import org.kde.plasma.core as PlasmaCore +import org.kde.plasma.plasmoid 2.0 + +PlasmoidItem { + id: root + + Layout.minimumWidth: internalContainmentItem ? internalContainmentItem.Layout.minimumWidth : 0 + Layout.minimumHeight: internalContainmentItem ? internalContainmentItem.Layout.minimumHeight : 0 + Layout.preferredHeight: Layout.minimumHeight + + preferredRepresentation: fullRepresentation + Plasmoid.status: internalContainmentItem ? internalContainmentItem.status : PlasmaCore.Types.UnknownStatus + + property Item internalContainmentItem + + Component.onCompleted: { + root.internalContainmentItem = plasmoid.internalContainmentItem; + + if (root.internalContainmentItem === null) { + return; + } + root.internalContainmentItem.anchors.fill = undefined; + root.internalContainmentItem.parent = root; + root.internalContainmentItem.anchors.fill = root; + } + + Connections { + target: plasmoid + function onInternalContainmentItemChanged() { + root.internalContainmentItem = plasmoid.internalContainmentItem; + root.internalContainmentItem.parent = root; + root.internalContainmentItem.anchors.fill = root; + } + } +} diff --git a/applets/grouping/container/package/metadata.json b/applets/grouping/container/package/metadata.json new file mode 100644 index 00000000..147fe32d --- /dev/null +++ b/applets/grouping/container/package/metadata.json @@ -0,0 +1,145 @@ +{ + "KPackageStructure": "Plasma/Applet", + "KPlugin": { + "Authors": [ + { + "Email": "davidedmundson@kde.org", + "Name": "David Edmundson", + "Name[ar]": "ديفيد إدموندسون", + "Name[az]": "David Edmundson", + "Name[bg]": "David Edmundson", + "Name[ca@valencia]": "David Edmundson", + "Name[ca]": "David Edmundson", + "Name[cs]": "David Edmundson", + "Name[da]": "David Edmundson", + "Name[de]": "David Edmundson", + "Name[en_GB]": "David Edmundson", + "Name[eo]": "David Edmundson", + "Name[es]": "David Edmundson", + "Name[eu]": "David Edmundson", + "Name[fi]": "David Edmundson", + "Name[fr]": "David Edmundson", + "Name[gl]": "David Edmundson", + "Name[he]": "דיויד אדמונדסון", + "Name[hu]": "David Edmundson", + "Name[ia]": "David Edmundson", + "Name[id]": "David Edmundson", + "Name[is]": "David Edmundson", + "Name[it]": "David Edmundson", + "Name[ja]": "David Edmundson", + "Name[ka]": "დავიდ ედმუნდსონი", + "Name[ko]": "David Edmundson", + "Name[lt]": "David Edmundson", + "Name[lv]": "David Edmundson", + "Name[nl]": "David Edmundson", + "Name[nn]": "David Edmundson", + "Name[pl]": "David Edmundson", + "Name[pt]": "David Edmundson", + "Name[pt_BR]": "David Edmundson", + "Name[ro]": "David Edmundson", + "Name[ru]": "David Edmundson", + "Name[sk]": "David Edmundson", + "Name[sl]": "David Edmundson", + "Name[sv]": "David Edmundson", + "Name[tr]": "David Edmundson", + "Name[uk]": "David Edmundson", + "Name[vi]": "David Edmundson", + "Name[x-test]": "xxDavid Edmundsonxx", + "Name[zh_CN]": "David Edmundson", + "Name[zh_TW]": "David Edmundson" + } + ], + "Category": "Windows and Tasks", + "Description": "Group Plasma widgets together", + "Description[ar]": "جمع ودجات بلازما معا", + "Description[az]": "Plasma vidjetlərini birlikdə qruplaşdırmaq", + "Description[bg]": "Групиране на уиджетите на Plasma заедно", + "Description[ca@valencia]": "Agrupa els ginys de Plasma", + "Description[ca]": "Agrupa els ginys del Plasma", + "Description[cs]": "Seskupit widgety Plasma dohromady", + "Description[da]": "Gruppér Plasma-widgets sammen", + "Description[de]": "Plasma-Miniprogramme zusammen gruppieren", + "Description[en_GB]": "Group Plasma widgets together", + "Description[eo]": "Grupigi Plasma-fenestraĵojn kune", + "Description[es]": "Agrupar widgets de Plasma", + "Description[eu]": "Taldekatu Plasma trepetak elkarrekin", + "Description[fi]": "Ryhmittele Plasma-sovelmia", + "Description[fr]": "Regrouper les composants graphiques de Plasma ensemble", + "Description[gl]": "Agrupar os trebellos de Plasma.", + "Description[he]": "קיבוץ יישומונים של פלזמה יחד", + "Description[hu]": "Plasma kisalkalmazások csoportosítása", + "Description[ia]": "Gruppa Widgets de Plasma insimul", + "Description[id]": "Kelompokkan widget Plasma bersama", + "Description[is]": "Safna Plasma-græjum saman í hóp", + "Description[it]": "Raggruppa gli oggetti di Plasma", + "Description[ja]": "Plasma ウィジェット同士をグループ化します", + "Description[ka]": "Plasma-ის ვიჯეტების ერთად დაჯგუფება", + "Description[ko]": "Plasma 위젯을 하나로 묶기", + "Description[lt]": "Kartu sugrupuoti Plasma valdiklius", + "Description[lv]": "Grupējot „Plasma“ logdaļas", + "Description[nl]": "Plasma-widgets tezamen groeperen", + "Description[nn]": "Grupper Plasma-element saman", + "Description[pl]": "Zgrupuj elementy interfejsu Plazmy razem", + "Description[pt]": "Agrupar elementos do Plasma em conjunto", + "Description[pt_BR]": "Agrupar os widgets do Plasma", + "Description[ro]": "Grupează împreună controale grafice Plasma", + "Description[ru]": "Объединяет виджеты в группу", + "Description[sk]": "Zoskupenie Plasma widgetov", + "Description[sl]": "Grupiraj Plasma gradnike skupaj", + "Description[sv]": "Gruppera grafiska plasmakomponenter", + "Description[tr]": "Plasma araç takımlarını grupla", + "Description[uk]": "Групувати віджети Плазми", + "Description[vi]": "Nhóm các phụ kiện Plasma lại với nhau", + "Description[x-test]": "xxGroup Plasma widgets togetherxx", + "Description[zh_CN]": "将多个 Plasma 挂件合并为一组", + "Description[zh_TW]": "組合數個 Plasma 元件", + "Icon": "object-group", + "Id": "org.kde.plasma.grouping", + "License": "GPL-2.0+", + "Name": "Grouping Plasmoid", + "Name[ar]": "تجميع البلازمويد", + "Name[az]": "Plasmoid qruplaşması", + "Name[bg]": "Групиране на приставки", + "Name[ca@valencia]": "Plasmoide d'agrupació", + "Name[ca]": "Plasmoide d'agrupació", + "Name[cs]": "Seskupovací Plasmoid", + "Name[da]": "Grupperer plasmoid", + "Name[de]": "Gruppierung von Miniprogrammen", + "Name[en_GB]": "Grouping Plasmoid", + "Name[eo]": "Grupo Plasmoid", + "Name[es]": "Plasmoide de agrupación", + "Name[eu]": "Taldekatzeko plasmoidea", + "Name[fi]": "Ryhmittelysovelma", + "Name[fr]": "Regroupement de composants graphiques", + "Name[gl]": "Trebello de agrupamento", + "Name[he]": "Plasmoid לקיבוץ", + "Name[hu]": "Csoportosító Plasmoid", + "Name[ia]": "Plasmoid de gruppar", + "Name[id]": "Pengelompok Plasmoid", + "Name[is]": "Hópunar-plasmíð", + "Name[it]": "Plasmoide di raggruppamento", + "Name[ja]": "ウィジェットのグループ化", + "Name[ka]": "პლაზმოიდების დაჯგუფება", + "Name[ko]": "그룹 Plasmoid", + "Name[lt]": "Plasma įskiepių grupavimas", + "Name[lv]": "Grupēšanas plazmoīds", + "Name[nl]": "Plasmoid-groepering", + "Name[nn]": "Grupperings­element", + "Name[pl]": "Plazmoid grupujący", + "Name[pt]": "Plasmóide de Agrupamento", + "Name[pt_BR]": "Plasmoide agrupado", + "Name[ro]": "Plasmoid de grupare", + "Name[ru]": "Группирующий виджет", + "Name[sk]": "Zoskupenie plasmoidov", + "Name[sl]": "Grouping Plasmoid", + "Name[sv]": "Grupperingsplasmoid", + "Name[tr]": "Gruplayan Plasmoid", + "Name[uk]": "Плазмоїд групування", + "Name[vi]": "Plasmoid nhóm", + "Name[x-test]": "xxGrouping Plasmoidxx", + "Name[zh_CN]": "Plasma 小程序分组", + "Name[zh_TW]": "群組元件", + "Website": "https://kde.org/plasma-desktop/" + }, + "X-Plasma-API-Minimum-Version": "6.0" +} diff --git a/applets/grouping/groupingcontainment.cpp b/applets/grouping/groupingcontainment.cpp new file mode 100644 index 00000000..86033ad2 --- /dev/null +++ b/applets/grouping/groupingcontainment.cpp @@ -0,0 +1,130 @@ +/* + * SPDX-FileCopyrightText: 2015 Marco Martin + * SPDX-FileCopyrightText: 2016 David Edmundson + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "groupingcontainment.h" + +#include +#include +#include +#include +#include +#include + +#include // Applet::actions + +GroupingContainment::GroupingContainment(QObject *parent, const KPluginMetaData &data, const QVariantList &args) + : Plasma::Containment(parent, data, args) +{ + setHasConfigurationInterface(true); +} + +void GroupingContainment::newTask(const QString &task) +{ + createApplet(task, QVariantList{QStringLiteral("org.kde.plasma:force-create")}); +} + +void GroupingContainment::cleanupTask(const QString &task) +{ + const auto appletList = applets(); + for (Plasma::Applet *applet : appletList) { + if (!applet->pluginMetaData().isValid() || task == applet->pluginMetaData().pluginId()) { + applet->destroy(); + } + } +} + +void GroupingContainment::showPlasmoidMenu(QQuickItem *appletInterface, int x, int y) +{ + if (!appletInterface) { + return; + } + + Plasma::Applet *applet = appletInterface->property("_plasma_applet").value(); + + QPointF pos = appletInterface->mapToScene(QPointF(x, y)); + + if (appletInterface->window() && appletInterface->window()->screen()) { + pos = appletInterface->window()->mapToGlobal(pos.toPoint()); + } else { + pos = QPoint(); + } + + QMenu *desktopMenu = new QMenu; + connect(this, &QObject::destroyed, desktopMenu, &QMenu::close); + desktopMenu->setAttribute(Qt::WA_DeleteOnClose); + + Q_EMIT applet->contextualActionsAboutToShow(); + const QList actions = applet->contextualActions(); + for (QAction *action : actions) { + if (action) { + desktopMenu->addAction(action); + } + } + + // add run associated action/ remove / alternatives + desktopMenu->addActions(applet->internalActions()); + + if (desktopMenu->isEmpty()) { + delete desktopMenu; + return; + } + + desktopMenu->adjustSize(); + + if (QScreen *screen = appletInterface->window()->screen()) { + const QRect geo = screen->availableGeometry(); + + pos = + QPoint(qBound(geo.left(), (int)pos.x(), geo.right() - desktopMenu->width()), qBound(geo.top(), (int)pos.y(), geo.bottom() - desktopMenu->height())); + } + + desktopMenu->popup(pos.toPoint()); +} + +QPointF GroupingContainment::popupPosition(QQuickItem *visualParent, int x, int y) +{ + if (!visualParent) { + return QPointF(0, 0); + } + + QPointF pos = visualParent->mapToScene(QPointF(x, y)); + + if (visualParent->window() && visualParent->window()->screen()) { + pos = visualParent->window()->mapToGlobal(pos.toPoint()); + } else { + return QPoint(); + } + return pos; +} + +void GroupingContainment::reorderItemBefore(QQuickItem *before, QQuickItem *after) +{ + if (!before || !after) { + return; + } + + before->setVisible(false); + before->setParentItem(after->parentItem()); + before->stackBefore(after); + before->setVisible(true); +} + +void GroupingContainment::reorderItemAfter(QQuickItem *after, QQuickItem *before) +{ + if (!before || !after) { + return; + } + + after->setVisible(false); + after->setParentItem(before->parentItem()); + after->stackAfter(before); + after->setVisible(true); +} + +K_PLUGIN_CLASS(GroupingContainment) + +#include "groupingcontainment.moc" diff --git a/applets/grouping/groupingcontainment.h b/applets/grouping/groupingcontainment.h new file mode 100644 index 00000000..e8c63446 --- /dev/null +++ b/applets/grouping/groupingcontainment.h @@ -0,0 +1,55 @@ +/* + * SPDX-FileCopyrightText: 2015 Marco Martin + * SPDX-FileCopyrightText: 2016 David Edmundson + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef GROUPINGCONTAINMENT_H +#define GROUPINGCONTAINMENT_H + +#include + +class QQuickItem; + +class GroupingContainment : public Plasma::Containment +{ + Q_OBJECT +public: + explicit GroupingContainment(QObject *parent, const KPluginMetaData &data, const QVariantList &args); + + // Creates an applet + Q_INVOKABLE void newTask(const QString &task); + + // cleans all instances of a given applet + void cleanupTask(const QString &task); + + /** + * Given an AppletInterface pointer, shows a proper context menu for it + */ + Q_INVOKABLE void showPlasmoidMenu(QQuickItem *appletInterface, int x, int y); + + /** + * Find out global coordinates for a popup given local MouseArea + * coordinates + */ + Q_INVOKABLE QPointF popupPosition(QQuickItem *visualParent, int x, int y); + + /** + * Reparent the item "before" with the same parent as the item "after", + * then restack it before it, using QQuickITem::stackBefore. + * used to quickly reorder icons in the systray (or hidden popup) + * @see QQuickITem::stackBefore + */ + Q_INVOKABLE void reorderItemBefore(QQuickItem *before, QQuickItem *after); + + /** + * Reparent the item "after" with the same parent as the item "before", + * then restack it after it, using QQuickITem::stackAfter. + * used to quickly reorder icons in the systray (or hidden popup) + * @see QQuickITem::stackAfter + */ + Q_INVOKABLE void reorderItemAfter(QQuickItem *after, QQuickItem *before); +}; + +#endif diff --git a/applets/grouping/package/contents/applet/CompactApplet.qml b/applets/grouping/package/contents/applet/CompactApplet.qml new file mode 100644 index 00000000..1052b791 --- /dev/null +++ b/applets/grouping/package/contents/applet/CompactApplet.qml @@ -0,0 +1,157 @@ +/* + * SPDX-FileCopyrightText: 2013 Marco Martin + * SPDX-FileCopyrightText: 2016 David Edmundson + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +import QtQuick 2.0 +import QtQuick.Layouts 1.1 +import QtQuick.Window 2.0 + +import org.kde.plasma.core as PlasmaCore +import org.kde.kirigami 2.20 as Kirigami +import org.kde.kquickcontrolsaddons 2.0 +import org.kde.plasma.plasmoid + +PlasmaCore.ToolTipArea { + id: root + objectName: "org.kde.desktop-CompactApplet" + anchors.fill: parent + + icon: plasmoid.icon + mainText: plasmoid.toolTipMainText + subText: plasmoid.toolTipSubText + location: plasmoid.location + active: !plasmoidItem.expanded + textFormat: plasmoid.toolTipTextFormat + mainItem: plasmoid.toolTipItem ? plasmoid.toolTipItem : null + property PlasmoidItem plasmoidItem + + property Item fullRepresentation + property Item compactRepresentation + + onCompactRepresentationChanged: { + if (compactRepresentation) { + compactRepresentation.parent = root; + compactRepresentation.anchors.fill = root; + compactRepresentation.visible = true; + } + root.visible = true; + } + + onFullRepresentationChanged: { + + if (!fullRepresentation) { + return; + } + //if the fullRepresentation size was restored to a stored size, or if is dragged from the desktop, restore popup size + if (fullRepresentation.width > 0) { + popupWindow.mainItem.width = Qt.binding(function() { + return fullRepresentation.width + }) + } else if (fullRepresentation.Layout && fullRepresentation.Layout.preferredWidth > 0) { + popupWindow.mainItem.width = Qt.binding(function() { + return fullRepresentation.Layout.preferredWidth + }) + } else if (fullRepresentation.implicitWidth > 0) { + popupWindow.mainItem.width = Qt.binding(function() { + return fullRepresentation.implicitWidth + }) + } else { + popupWindow.mainItem.width = Qt.binding(function() { + return Kirigami.Theme.gridUnit * 35 + }) + } + + if (fullRepresentation.height > 0) { + popupWindow.mainItem.height = Qt.binding(function() { + return fullRepresentation.height + }) + } else if (fullRepresentation.Layout && fullRepresentation.Layout.preferredHeight > 0) { + popupWindow.mainItem.height = Qt.binding(function() { + return fullRepresentation.Layout.preferredHeight + }) + } else if (fullRepresentation.implicitHeight > 0) { + popupWindow.mainItem.height = Qt.binding(function() { + return fullRepresentation.implicitHeight + }) + } else { + popupWindow.mainItem.height = Qt.binding(function() { + return Kirigami.Theme.gridUnit * 25 + }) + } + + fullRepresentation.parent = appletParent; + fullRepresentation.anchors.fill = fullRepresentation.parent; + } + + Timer { + id: expandedSync + interval: 100 + onTriggered: plasmoidItem.expanded = popupWindow.visible; + } + + Connections { + target: plasmoid.internalAction("configure") + function onTriggered() { + plasmoidItem.expanded = false + } + } + + Connections { + target: plasmoid + function onContextualActionsAboutToShow() { + root.hideToolTip() + } + } + + PlasmaCore.Dialog { + id: popupWindow + objectName: "popupWindow" + flags: Qt.WindowStaysOnTopHint + visible: plasmoidItem.expanded && fullRepresentation + visualParent: compactRepresentation ? compactRepresentation : null + location: plasmoid.location + hideOnWindowDeactivate: root.plasmoidItem.hideOnWindowDeactivate + + property var oldStatus: PlasmaCore.Types.UnknownStatus + + //It's a MouseEventListener to get all the events, so the eventfilter will be able to catch them + mainItem: MouseEventListener { + id: appletParent + + focus: true + + Keys.onEscapePressed: { + plasmoidItem.expanded = false; + } + + LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft + LayoutMirroring.childrenInherit: true + + Layout.minimumWidth: (fullRepresentation && fullRepresentation.Layout) ? fullRepresentation.Layout.minimumWidth : 0 + Layout.minimumHeight: (fullRepresentation && fullRepresentation.Layout) ? fullRepresentation.Layout.minimumHeight: 0 + Layout.maximumWidth: (fullRepresentation && fullRepresentation.Layout) ? fullRepresentation.Layout.maximumWidth : Infinity + Layout.maximumHeight: (fullRepresentation && fullRepresentation.Layout) ? fullRepresentation.Layout.maximumHeight: Infinity + + onActiveFocusChanged: { + if (activeFocus && fullRepresentation) { + fullRepresentation.forceActiveFocus() + } + } + } + + onVisibleChanged: { + if (!visible) { + expandedSync.restart(); + plasmoid.status = oldStatus; + } else { + oldStatus = plasmoid.status; + plasmoid.status = PlasmaCore.Types.RequiresAttentionStatus; + // This call currently fails and complains at runtime: + // QWindow::setWindowState: QWindow::setWindowState does not accept Qt::WindowActive + popupWindow.requestActivate(); + } + } + } +} diff --git a/applets/grouping/package/contents/config/config.qml b/applets/grouping/package/contents/config/config.qml new file mode 100644 index 00000000..3456c7a9 --- /dev/null +++ b/applets/grouping/package/contents/config/config.qml @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2013 Sebastian Kügler + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.0 + +import org.kde.plasma.configuration 2.0 + +ConfigModel { +} diff --git a/applets/grouping/package/contents/config/main.xml b/applets/grouping/package/contents/config/main.xml new file mode 100644 index 00000000..0c37a598 --- /dev/null +++ b/applets/grouping/package/contents/config/main.xml @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/applets/grouping/package/contents/ui/items/AbstractItem.qml b/applets/grouping/package/contents/ui/items/AbstractItem.qml new file mode 100644 index 00000000..8483ee7c --- /dev/null +++ b/applets/grouping/package/contents/ui/items/AbstractItem.qml @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2016 Marco Martin + * SPDX-FileCopyrightText: 2016 David Edmundson + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +import QtQuick 2.15 +import org.kde.plasma.core as PlasmaCore + +MouseArea { + id: abstractItem + property string text + property string itemId + property int status //PlasmaCore.Types.ItemStatus + property bool active + + hoverEnabled: true + drag.filterChildren: true + acceptedButtons: Qt.RightButton +} + diff --git a/applets/grouping/package/contents/ui/items/PlasmoidItem.qml b/applets/grouping/package/contents/ui/items/PlasmoidItem.qml new file mode 100644 index 00000000..f5c7236e --- /dev/null +++ b/applets/grouping/package/contents/ui/items/PlasmoidItem.qml @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: 2015 Marco Martin + * SPDX-FileCopyrightText: 2016 David Edmundson + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +import QtQuick 2.15 +import org.kde.plasma.core as PlasmaCore + +AbstractItem { + id: plasmoidContainer + + property Item applet + text: applet ? applet.plasmoid.title : "" + + itemId: applet ? applet.plasmoid.id : "" + status: applet ? applet.plasmoid.status : PlasmaCore.Types.UnknownStatus + active: root.activeApplet !== applet + + onClicked: { + if (applet) { + if (mouse.button === Qt.RightButton) { + plasmoid.showPlasmoidMenu(applet, mouse.x, mouse.y); + } + } + } +} diff --git a/applets/grouping/package/contents/ui/main.qml b/applets/grouping/package/contents/ui/main.qml new file mode 100644 index 00000000..1413510e --- /dev/null +++ b/applets/grouping/package/contents/ui/main.qml @@ -0,0 +1,153 @@ +/* + * SPDX-FileCopyrightText: 2011 Marco Martin + * SPDX-FileCopyrightText: 2016 David Edmundson + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +import QtQuick 2.15 +import QtQuick.Layouts 1.15 + +import org.kde.plasma.core as PlasmaCore +import org.kde.kirigami 2.20 as Kirigami +import org.kde.plasma.components 3.0 as PlasmaComponents +import org.kde.plasma.plasmoid 2.0 +import org.kde.draganddrop 2.0 as DnD + +import "items" + +ContainmentItem { + id: root + + //be at least the same size as the system tray popup + Layout.minimumWidth: Kirigami.Units.gridUnit * 24 + Layout.minimumHeight: Kirigami.Units.gridUnit * 21 + Layout.preferredWidth: Layout.minimumWidth + Layout.preferredHeight: Layout.minimumHeight * 1.5 + + property Component plasmoidItemComponent + + Containment.onAppletAdded: { + addApplet(applet); + //when we add an applet, select it straight away + //we know it will always be at the end of the stack + tabbar.currentIndex = mainStack.count -1; + } + Containment.onAppletRemoved: { + for (var i = 0; i < mainStack.count; i++) { + if (mainStack.children[i].itemId === applet.id) { + mainStack.children[i].destroy(); + break; + } + } + } + + function addApplet(applet) { + const appletItem = root.itemFor(applet); + + if (!plasmoidItemComponent) { + plasmoidItemComponent = Qt.createComponent("items/PlasmoidItem.qml"); + } + + if (plasmoidItemComponent.status === Component.Error) { + console.warn("Could not create PlasmoidItem", plasmoidItemComponent.errorString()); + } + + var plasmoidContainer = plasmoidItemComponent.createObject(mainStack, { "applet": appletItem }); + + appletItem.anchors.fill = undefined; + appletItem.parent = plasmoidContainer; + appletItem.anchors.fill = plasmoidContainer; + appletItem.visible = true; + } + + Component.onCompleted: { + var applets = Containment.applets; + for (var i = 0 ; i < applets.length; i++) { + addApplet(applets[i]); + } + } + + Item { + anchors.fill: parent + PlasmaComponents.TabBar { + id: tabbar + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft + LayoutMirroring.childrenInherit: true + + Repeater { + model: mainStack.children + + //attached properties: + // model == a QQmlDMObjectData wrapper round the PlasmoidItem + // modelData == the PlasmoidItem instance + PlasmaComponents.TabButton { + text: model.text + MouseArea { + acceptedButtons: Qt.RightButton + anchors.fill: parent + onClicked: { + modelData.clicked(mouse); + } + } + } + } + //hack: PlasmaComponents.TabBar is being weird with heights. Probably a bug + height: contentChildren[0] ? contentChildren[0].height : undefined + } + + StackLayout { + id: mainStack + currentIndex: tabbar.currentIndex + anchors.top: tabbar.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + } + } + + DnD.DropArea { + anchors.fill: parent + + preventStealing: true + + /** Extracts the name of the applet in the drag data if present + * otherwise returns null*/ + function appletName(event) { + if (event.mimeData.formats.indexOf("text/x-plasmoidservicename") < 0) { + return null; + } + var plasmoidId = event.mimeData.getDataAsByteArray("text/x-plasmoidservicename"); + return plasmoidId; + } + + onDragEnter: { + if (!appletName(event)) { + event.ignore(); + } + } + + onDrop: { + var plasmoidId = appletName(event); + if (!plasmoidId) { + event.ignore(); + return; + } + plasmoid.newTask(plasmoidId); + } + } + + PlasmaComponents.Label { + anchors.fill: mainStack + text: i18n("Drag applets here") + textFormat: Text.PlainText + visible: mainStack.count === 0 + elide: Text.ElideRight + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } +} diff --git a/applets/grouping/package/metadata.json b/applets/grouping/package/metadata.json new file mode 100644 index 00000000..11d2ee3d --- /dev/null +++ b/applets/grouping/package/metadata.json @@ -0,0 +1,148 @@ +{ + "KPackageStructure": "Plasma/Applet", + "KPlugin": { + "Authors": [ + { + "Email": "davidedmundson@kde.org", + "Name": "David Edmundson", + "Name[ar]": "ديفيد إدموندسون", + "Name[az]": "David Edmundson", + "Name[bg]": "David Edmundson", + "Name[ca@valencia]": "David Edmundson", + "Name[ca]": "David Edmundson", + "Name[cs]": "David Edmundson", + "Name[da]": "David Edmundson", + "Name[de]": "David Edmundson", + "Name[en_GB]": "David Edmundson", + "Name[eo]": "David Edmundson", + "Name[es]": "David Edmundson", + "Name[eu]": "David Edmundson", + "Name[fi]": "David Edmundson", + "Name[fr]": "David Edmundson", + "Name[gl]": "David Edmundson", + "Name[he]": "דיויד אדמונדסון", + "Name[hu]": "David Edmundson", + "Name[ia]": "David Edmundson", + "Name[id]": "David Edmundson", + "Name[is]": "David Edmundson", + "Name[it]": "David Edmundson", + "Name[ja]": "David Edmundson", + "Name[ka]": "დავიდ ედმუნდსონი", + "Name[ko]": "David Edmundson", + "Name[lt]": "David Edmundson", + "Name[lv]": "David Edmundson", + "Name[nl]": "David Edmundson", + "Name[nn]": "David Edmundson", + "Name[pl]": "David Edmundson", + "Name[pt]": "David Edmundson", + "Name[pt_BR]": "David Edmundson", + "Name[ro]": "David Edmundson", + "Name[ru]": "David Edmundson", + "Name[sk]": "David Edmundson", + "Name[sl]": "David Edmundson", + "Name[sv]": "David Edmundson", + "Name[tr]": "David Edmundson", + "Name[uk]": "David Edmundson", + "Name[vi]": "David Edmundson", + "Name[x-test]": "xxDavid Edmundsonxx", + "Name[zh_CN]": "David Edmundson", + "Name[zh_TW]": "David Edmundson" + } + ], + "BugReportUrl": "https://bugs.kde.org/enter_bug.cgi?product=kdeplasma-addons&component=Grouping", + "Category": "Windows and Tasks", + "Description": "Group Plasma widgets together", + "Description[ar]": "جمع ودجات بلازما معا", + "Description[az]": "Plasma vidjetlərini birlikdə qruplaşdırmaq", + "Description[bg]": "Групиране на уиджетите на Plasma заедно", + "Description[ca@valencia]": "Agrupa els ginys de Plasma", + "Description[ca]": "Agrupa els ginys del Plasma", + "Description[cs]": "Seskupit widgety Plasma dohromady", + "Description[da]": "Gruppér Plasma-widgets sammen", + "Description[de]": "Plasma-Miniprogramme zusammen gruppieren", + "Description[en_GB]": "Group Plasma widgets together", + "Description[eo]": "Grupigi Plasma-fenestraĵojn kune", + "Description[es]": "Agrupar widgets de Plasma", + "Description[eu]": "Taldekatu Plasma trepetak elkarrekin", + "Description[fi]": "Ryhmittele Plasma-sovelmia", + "Description[fr]": "Regrouper les composants graphiques de Plasma ensemble", + "Description[gl]": "Agrupar os trebellos de Plasma.", + "Description[he]": "קיבוץ יישומונים של פלזמה יחד", + "Description[hu]": "Plasma kisalkalmazások csoportosítása", + "Description[ia]": "Gruppa Widgets de Plasma insimul", + "Description[id]": "Kelompokkan widget Plasma bersama", + "Description[is]": "Safna Plasma-græjum saman í hóp", + "Description[it]": "Raggruppa gli oggetti di Plasma", + "Description[ja]": "Plasma ウィジェット同士をグループ化します", + "Description[ka]": "Plasma-ის ვიჯეტების ერთად დაჯგუფება", + "Description[ko]": "Plasma 위젯을 하나로 묶기", + "Description[lt]": "Kartu sugrupuoti Plasma valdiklius", + "Description[lv]": "Grupējot „Plasma“ logdaļas", + "Description[nl]": "Plasma-widgets tezamen groeperen", + "Description[nn]": "Grupper Plasma-element saman", + "Description[pl]": "Zgrupuj elementy interfejsu Plazmy razem", + "Description[pt]": "Agrupar elementos do Plasma em conjunto", + "Description[pt_BR]": "Agrupar os widgets do Plasma", + "Description[ro]": "Grupează împreună controale grafice Plasma", + "Description[ru]": "Объединяет виджеты в группу", + "Description[sk]": "Zoskupenie Plasma widgetov", + "Description[sl]": "Grupiraj Plasma gradnike skupaj", + "Description[sv]": "Gruppera grafiska plasmakomponenter", + "Description[tr]": "Plasma araç takımlarını grupla", + "Description[uk]": "Групувати віджети Плазми", + "Description[vi]": "Nhóm các phụ kiện Plasma lại với nhau", + "Description[x-test]": "xxGroup Plasma widgets togetherxx", + "Description[zh_CN]": "将多个 Plasma 挂件合并为一组", + "Description[zh_TW]": "組合數個 Plasma 元件", + "Icon": "object-group", + "Id": "org.kde.plasma.private.grouping", + "License": "GPL-2.0+", + "Name": "Grouping Plasmoid", + "Name[ar]": "تجميع البلازمويد", + "Name[az]": "Plasmoid qruplaşması", + "Name[bg]": "Групиране на приставки", + "Name[ca@valencia]": "Plasmoide d'agrupació", + "Name[ca]": "Plasmoide d'agrupació", + "Name[cs]": "Seskupovací Plasmoid", + "Name[da]": "Grupperer plasmoid", + "Name[de]": "Gruppierung von Miniprogrammen", + "Name[en_GB]": "Grouping Plasmoid", + "Name[eo]": "Grupo Plasmoid", + "Name[es]": "Plasmoide de agrupación", + "Name[eu]": "Taldekatzeko plasmoidea", + "Name[fi]": "Ryhmittelysovelma", + "Name[fr]": "Regroupement de composants graphiques", + "Name[gl]": "Trebello de agrupamento", + "Name[he]": "Plasmoid לקיבוץ", + "Name[hu]": "Csoportosító Plasmoid", + "Name[ia]": "Plasmoid de gruppar", + "Name[id]": "Pengelompok Plasmoid", + "Name[is]": "Hópunar-plasmíð", + "Name[it]": "Plasmoide di raggruppamento", + "Name[ja]": "ウィジェットのグループ化", + "Name[ka]": "პლაზმოიდების დაჯგუფება", + "Name[ko]": "그룹 Plasmoid", + "Name[lt]": "Plasma įskiepių grupavimas", + "Name[lv]": "Grupēšanas plazmoīds", + "Name[nl]": "Plasmoid-groepering", + "Name[nn]": "Grupperings­element", + "Name[pl]": "Plazmoid grupujący", + "Name[pt]": "Plasmóide de Agrupamento", + "Name[pt_BR]": "Plasmoide agrupado", + "Name[ro]": "Plasmoid de grupare", + "Name[ru]": "Группирующий виджет", + "Name[sk]": "Zoskupenie plasmoidov", + "Name[sl]": "Grouping Plasmoid", + "Name[sv]": "Grupperingsplasmoid", + "Name[tr]": "Gruplayan Plasmoid", + "Name[uk]": "Плазмоїд групування", + "Name[vi]": "Plasmoid nhóm", + "Name[x-test]": "xxGrouping Plasmoidxx", + "Name[zh_CN]": "Plasma 小程序分组", + "Name[zh_TW]": "群組元件", + "Website": "https://kde.org/plasma-desktop/" + }, + "NoDisplay": true, + "X-Plasma-API-Minimum-Version": "6.0", + "X-Plasma-ContainmentType": "CustomEmbedded" +} diff --git a/applets/katesessions/Messages.sh b/applets/katesessions/Messages.sh new file mode 100644 index 00000000..d5917990 --- /dev/null +++ b/applets/katesessions/Messages.sh @@ -0,0 +1,2 @@ +#! /usr/bin/env bash +$XGETTEXT `find . -name \*.qml` -o $podir/plasma_applet_org.kde.plasma.addons.katesessions.pot diff --git a/applets/katesessions/contents/ui/KateSessionsItemDelegate.qml b/applets/katesessions/contents/ui/KateSessionsItemDelegate.qml new file mode 100644 index 00000000..0c331773 --- /dev/null +++ b/applets/katesessions/contents/ui/KateSessionsItemDelegate.qml @@ -0,0 +1,121 @@ +/* + SPDX-FileCopyrightText: 2014 Joseph Wenninger + SPDX-FileCopyrightText: 2022 Alexander Lohnau + + Based on the clipboard applet: + SPDX-FileCopyrightText: 2014 Martin Gräßlin + SPDX-FileCopyrightText: 2014 Sebastian Kügler + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +import QtQuick 2.0 +import QtQuick.Layouts 1.1 +import org.kde.plasma.components 3.0 as PlasmaComponents3 +import org.kde.kquickcontrolsaddons 2.0 as KQuickControlsAddons +import org.kde.kirigami 2.20 as Kirigami + +import org.kde.plasma.private.profiles 1.0 + +PlasmaComponents3.ItemDelegate { + id: menuItem + + signal itemSelected(string profileIdentifier) + + property bool showInput: false + + height: Math.max(label.height, sessionnameditlayout.implicitHeight) + Kirigami.Units.smallSpacing + + MouseArea { + anchors.fill: parent + hoverEnabled: true + + onClicked: { + if (profileIdentifier !== "") + menuItem.itemSelected(profileIdentifier); + else { + showInput=true; + sessionname.forceActiveFocus(Qt.MouseFocusReason); + } + } + onEntered: menuListView.currentIndex = index + onExited: menuListView.currentIndex = -1 + + Item { + id: label + height: iconItem.height + anchors { + left: parent.left + leftMargin: Kirigami.Units.smallSpacing + right: parent.right + verticalCenter: parent.verticalCenter + } + + PlasmaComponents3.Label { + anchors { + left: parent.left + right: parent.right + rightMargin: Kirigami.Units.gridUnit * 2 + leftMargin: Kirigami.Units.iconSizes.small + Kirigami.Units.smallSpacing * 2 + verticalCenter: parent.verticalCenter + } + maximumLineCount: 1 + text: name.trim() + textFormat: Text.PlainText + visible: !showInput + elide: Text.ElideRight + wrapMode: Text.Wrap + } + + Kirigami.Icon { + id: iconItem + width: Kirigami.Units.iconSizes.small + height: width + anchors.verticalCenter: parent.verticalCenter + source: iconName + } + } + + RowLayout { + id:sessionnameditlayout + visible:showInput + height: implicitHeight + anchors { + left: parent.left + right: parent.right + rightMargin: 0 + leftMargin: Kirigami.Units.iconSizes.small + Kirigami.Units.smallSpacing * 2 + verticalCenter: parent.verticalCenter + } + + PlasmaComponents3.TextField { + id: sessionname + placeholderText: i18n("Session name") + clearButtonShown: true + Layout.fillWidth: true + Keys.onReturnPressed: {menuItem.itemSelected(sessionname.text.replace(/^\s+|\s+$/g, '')); showInput=false;} + } + + PlasmaComponents3.ToolButton { + icon.name: "dialog-ok" + enabled: sessionname.text.replace(/^\s+|\s+$/g, '').length>0 + onClicked: {menuItem.itemSelected(sessionname.text.replace(/^\s+|\s+$/g, '')); showInput=false;} + + PlasmaComponents3.ToolTip { + text: i18n("Create new session and start Kate") + } + } + + PlasmaComponents3.ToolButton { + icon.name: "dialog-cancel" + onClicked: { + showInput=false; + sessionname.text=''; + } + + PlasmaComponents3.ToolTip { + text: i18n("Cancel session creation") + } + } + } + } +} diff --git a/applets/katesessions/contents/ui/Menu.qml b/applets/katesessions/contents/ui/Menu.qml new file mode 100644 index 00000000..ba8a0d1a --- /dev/null +++ b/applets/katesessions/contents/ui/Menu.qml @@ -0,0 +1,42 @@ +/* + SPDX-FileCopyrightText: 2014 Joseph Wenninger + SPDX-FileCopyrightText: 2022 Alexander Lohnau + + Based on the clipboard applet: + SPDX-FileCopyrightText: 2014 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +import QtQuick 2.0 +import org.kde.plasma.extras 2.0 as PlasmaExtras +import org.kde.plasma.components 3.0 as PlasmaComponents3 + +PlasmaComponents3.ScrollView { + id: menu + property alias view: menuListView + property alias model: menuListView.model + signal itemSelected(string profileIdentifier) + + ListView { + id: menuListView + focus: true + + boundsBehavior: Flickable.StopAtBounds + interactive: contentHeight > height + highlight: PlasmaExtras.Highlight { + anchors.bottomMargin: -listMargins.bottom + y: 1 + } + highlightMoveDuration: 0 + highlightResizeDuration: 0 + currentIndex: -1 + + delegate: KateSessionsItemDelegate { + width: menuListView.width + + onItemSelected: function(profileIdentifier) { + menu.itemSelected(profileIdentifier) + } + } + } +} diff --git a/applets/katesessions/contents/ui/main.qml b/applets/katesessions/contents/ui/main.qml new file mode 100644 index 00000000..8a193873 --- /dev/null +++ b/applets/katesessions/contents/ui/main.qml @@ -0,0 +1,148 @@ +/* + SPDX-FileCopyrightText: 2014 Joseph Wenninger + SPDX-FileCopyrightText: 2022 Alexander Lohnau + + Based on the clipboard applet: + SPDX-FileCopyrightText: 2014 Martin Gräßlin + SPDX-FileCopyrightText: 2014 Kai Uwe Broulik + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +import QtQuick 2.0 +import QtQuick.Layouts 1.1 + +import org.kde.plasma.plasmoid 2.0 +import org.kde.plasma.core as PlasmaCore +import org.kde.kirigami 2.20 as Kirigami +import org.kde.ksvg 1.0 as KSvg +import org.kde.kitemmodels 1.0 as KItemModels +import org.kde.plasma.components 3.0 as PlasmaComponents3 +import org.kde.plasma.extras 2.0 as PlasmaExtras + +import org.kde.plasma.private.profiles 1.0 + +PlasmoidItem { + id: main + + readonly property bool inPanel: (Plasmoid.location === PlasmaCore.Types.TopEdge + || Plasmoid.location === PlasmaCore.Types.RightEdge + || Plasmoid.location === PlasmaCore.Types.BottomEdge + || Plasmoid.location === PlasmaCore.Types.LeftEdge) + + width: (Plasmoid.formFactor==PlasmaCore.Types.Planar)? Kirigami.Units.gridUnit * 14 : undefined + height: (Plasmoid.formFactor==PlasmaCore.Types.Planar)? Kirigami.Units.gridUnit * 16: undefined + + switchWidth: Kirigami.Units.gridUnit * 11 + switchHeight: Kirigami.Units.gridUnit * 11 + Plasmoid.status: PlasmaCore.Types.ActiveStatus + Plasmoid.icon: inPanel ? "kate-symbolic" : "kate" + toolTipMainText: i18n("Kate Sessions") + + Component.onCompleted: { + plasmoid.removeInternalAction("configure"); + } + + property var searchHeader: PlasmaExtras.PlasmoidHeading { + PlasmaComponents3.TextField { + id: filter + placeholderText: i18n("Search…") + clearButtonShown: true + anchors.fill: parent + } + } + + fullRepresentation: PlasmaComponents3.Page { + + id: dialogItem + Layout.minimumWidth: Kirigami.Units.gridUnit * 12 + Layout.minimumHeight: Kirigami.Units.gridUnit * 12 + Layout.preferredWidth: Kirigami.Units.gridUnit * 16 + Layout.preferredHeight: Kirigami.Units.gridUnit * 24 + + focus: true + header: searchHeader + + property alias listMargins: listItemSvg.margins + + KSvg.FrameSvgItem { + id : listItemSvg + imagePath: "widgets/listitem" + prefix: "normal" + visible: false + } + + Keys.onPressed: { + switch(event.key) { + case Qt.Key_Up: { + sessionsMenu.view.decrementCurrentIndex(); + event.accepted = true; + break; + } + case Qt.Key_Down: { + sessionsMenu.view.incrementCurrentIndex(); + event.accepted = true; + break; + } + case Qt.Key_Enter: + case Qt.Key_Return: { + if (sessionsMenu.view.currentIndex >= 0) { + const profileIdentifier = sessionsMenu.model.get(sessionsMenu.view.currentIndex).profileIdentifier; + if (profileIdentifier) { + model.openProfile(profileIdentifier); + sessionsMenu.view.currentIndex = 0; + } + } + break; + } + case Qt.Key_Escape: { + if (filter.text == "") { + main.expanded = false; + } else { + filter.text = ""; + } + event.accepted = true; + break; + } + default: { // forward key to filter + // filter.text += event.text wil break if the key is backspace + if (event.key == Qt.Key_Backspace && filter.text == "") { + return; + } + if (event.text != "" && !filter.activeFocus) { + sessionsMenu.view.currentIndex = -1 + if (event.text == "v" && event.modifiers & Qt.ControlModifier) { + filter.paste(); + } else { + filter.text = ""; + filter.text += event.text; + } + filter.forceActiveFocus(); + event.accepted = true; + } + } + } + } + ColumnLayout { + anchors.fill: parent + Menu { + id: sessionsMenu + model: KItemModels.KSortFilterProxyModel { + sourceModel: ProfilesModel { + id: model + appName: "kate" + } + filterRoleName: "name" + filterString: filter.text + filterCaseSensitivity: Qt.CaseInsensitive + } + Layout.fillWidth: true + Layout.fillHeight: true + Layout.topMargin: Kirigami.Units.smallSpacing + onItemSelected: function (profileIdentifier) { + model.openProfile(profileIdentifier) + main.expanded = false; + } + } + } + } +} diff --git a/applets/katesessions/metadata.json b/applets/katesessions/metadata.json new file mode 100644 index 00000000..0efa728e --- /dev/null +++ b/applets/katesessions/metadata.json @@ -0,0 +1,131 @@ +{ + "KPackageStructure": "Plasma/Applet", + "KPlugin": { + "Authors": [ + { + "Email": "jowenn@kde.org", + "Name": "Joseph Wenninger", + "Name[ar]": "جوزيف وينينجر", + "Name[az]": "Joseph Wenninger", + "Name[bg]": "Joseph Wenninger", + "Name[ca@valencia]": "Joseph Wenninger", + "Name[ca]": "Joseph Wenninger", + "Name[cs]": "Joseph Wenninger", + "Name[da]": "Joseph Wenninger", + "Name[de]": "Joseph Wenninger", + "Name[en_GB]": "Joseph Wenninger", + "Name[eo]": "Joseph Wenninger", + "Name[es]": "Joseph Wenninger", + "Name[eu]": "Joseph Wenninger", + "Name[fi]": "Joseph Wenninger", + "Name[fr]": "Joseph Wenninger", + "Name[gl]": "Joseph Wenninger", + "Name[he]": "ג׳וזף ונינגר", + "Name[hu]": "Joseph Wenninger", + "Name[ia]": "Joseph Wenninger", + "Name[id]": "Joseph Wenninger", + "Name[is]": "Joseph Wenninger", + "Name[it]": "Joseph Wenninger", + "Name[ja]": "Joseph Wenninger", + "Name[ka]": "Joseph Wenninger", + "Name[ko]": "Joseph Wenninger", + "Name[lt]": "Joseph Wenninger", + "Name[lv]": "Joseph Wenninger", + "Name[nl]": "Joseph Wenninger", + "Name[nn]": "Joseph Wenninger", + "Name[pl]": "Joseph Wenninger", + "Name[pt]": "Joseph Wenninger", + "Name[pt_BR]": "Joseph Wenninger", + "Name[ro]": "Joseph Wenninger", + "Name[ru]": "Joseph Wenninger", + "Name[sk]": "Joseph Wenninger", + "Name[sl]": "Joseph Wenninger", + "Name[sv]": "Joseph Wenninger", + "Name[tr]": "Joseph Wenninger", + "Name[uk]": "Joseph Wenninger", + "Name[vi]": "Joseph Wenninger", + "Name[x-test]": "xxJoseph Wenningerxx", + "Name[zh_CN]": "Joseph Wenninger", + "Name[zh_TW]": "Joseph Wenninger" + } + ], + "BugReportUrl": "https://bugs.kde.org/enter_bug.cgi?product=kdeplasma-addons&component=Kate Sessions", + "Category": "Utilities", + "Description": "Kate session launcher", + "Description[ar]": "مُطلق جلسات كيت", + "Description[bg]": "Зареждане на сесии на Kate", + "Description[ca@valencia]": "Iniciador de sessió de Kate", + "Description[ca]": "Llançador de sessió del Kate", + "Description[cs]": "Spouštěč sezení Kate", + "Description[de]": "Kate-Sitzung starten", + "Description[en_GB]": "Kate session launcher", + "Description[es]": "Lanzador de sesiones de Kate", + "Description[eu]": "Kate-saio abiarazlea", + "Description[fr]": "Lanceur de sessions pour Kate", + "Description[gl]": "Iniciador de sesións de Kate", + "Description[he]": "מריץ הפעלות של Kate", + "Description[hu]": "Kate munkamenet-indító", + "Description[ia]": "Lanceator de session de Kate", + "Description[it]": "Avviatore delle sessioni di Kate", + "Description[ka]": "Kate-ის სესიის გამშვები", + "Description[nn]": "Start Kate-økter", + "Description[pl]": "Uruchamiacz posiedzenia Kate", + "Description[ru]": "Программа запуска сеанса Kate", + "Description[sl]": "Zaganjalnik sej Kate", + "Description[sv]": "Kate sessionsstart", + "Description[tr]": "Kate oturumu başlatıcısı", + "Description[uk]": "Засіб запуску сеансів Kate", + "Description[x-test]": "xxKate session launcherxx", + "Description[zh_CN]": "Kate 会话启动器", + "Description[zh_TW]": "Kate 工作階段啟動器", + "Icon": "kate", + "Id": "org.kde.plasma.addons.katesessions", + "License": "GPL-2.0+", + "Name": "Kate Sessions", + "Name[ar]": "جلسات كيت", + "Name[az]": "Kate sessiyaları", + "Name[bg]": "Сесии на Kate", + "Name[ca@valencia]": "Sessions de Kate", + "Name[ca]": "Sessions del Kate", + "Name[cs]": "Sezení Kate", + "Name[da]": "Kate-sessioner", + "Name[de]": "Kate-Sitzungen", + "Name[en_GB]": "Kate Sessions", + "Name[eo]": "Kate Seancoj", + "Name[es]": "Sesiones de Kate", + "Name[eu]": "Kate saioak", + "Name[fi]": "Kate-istunnot", + "Name[fr]": "Sessions de Kate", + "Name[gl]": "Sesións de Kate", + "Name[he]": "הפעלות של Kate", + "Name[hu]": "Kate munkamenetek", + "Name[ia]": "Sessiones de Kate", + "Name[id]": "Sesi Kate", + "Name[is]": "Kate setur", + "Name[it]": "Sessioni di Kate", + "Name[ja]": "Kate セッション", + "Name[ka]": "Kate-ის სესიები", + "Name[ko]": "Kate 세션", + "Name[lt]": "Kate seansai", + "Name[lv]": "„Kate“ sesijas", + "Name[nl]": "Kate-sessies", + "Name[nn]": "Kate-øktveljar", + "Name[pl]": "Sesje Kate", + "Name[pt]": "Sessões do Kate", + "Name[pt_BR]": "Sessões do Kate", + "Name[ro]": "Sesiuni Kate", + "Name[ru]": "Kate: сеансы", + "Name[sk]": "Sedenia Kate", + "Name[sl]": "Seje Kate", + "Name[sv]": "Kate-sessioner", + "Name[tr]": "Kate Oturumları", + "Name[uk]": "Сеанси Kate", + "Name[vi]": "Các phiên Kate", + "Name[x-test]": "xxKate Sessionsxx", + "Name[zh_CN]": "Kate 会话", + "Name[zh_TW]": "Kate 工作階段", + "Website": "https://kde.org/plasma-desktop" + }, + "X-Plasma-API-Minimum-Version": "6.0", + "X-Plasma-NotificationAreaCategory": "ApplicationStatus" +} diff --git a/applets/keyboardindicator/Messages.sh b/applets/keyboardindicator/Messages.sh new file mode 100644 index 00000000..c1e49dba --- /dev/null +++ b/applets/keyboardindicator/Messages.sh @@ -0,0 +1,2 @@ +#! /usr/bin/env bash +$XGETTEXT `find . -name \*.qml` -o $podir/plasma_applet_org.kde.plasma.keyboardindicator.pot diff --git a/applets/keyboardindicator/contents/config/config.qml b/applets/keyboardindicator/contents/config/config.qml new file mode 100644 index 00000000..d2d38f12 --- /dev/null +++ b/applets/keyboardindicator/contents/config/config.qml @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2017 Bernhard Friedreich + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.0 + +import org.kde.plasma.configuration 2.0 + +ConfigModel { + ConfigCategory { + name: i18nc("@title", "Keys") + icon: "input-caps-on" + source: "configAppearance.qml" + } +} diff --git a/applets/keyboardindicator/contents/config/main.xml b/applets/keyboardindicator/contents/config/main.xml new file mode 100644 index 00000000..19f7cf1b --- /dev/null +++ b/applets/keyboardindicator/contents/config/main.xml @@ -0,0 +1,14 @@ + + + + + + + Caps Lock + + + + diff --git a/applets/keyboardindicator/contents/ui/configAppearance.qml b/applets/keyboardindicator/contents/ui/configAppearance.qml new file mode 100644 index 00000000..f18e2a31 --- /dev/null +++ b/applets/keyboardindicator/contents/ui/configAppearance.qml @@ -0,0 +1,51 @@ +/* + * SPDX-FileCopyrightText: 2018 Aleix Pol Gonzalez + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.15 +import QtQuick.Controls 2.15 as Controls + +import org.kde.kirigami 2.20 as Kirigami +import org.kde.kcmutils as KCM + +KCM.SimpleKCM { + id: root + + signal configurationChanged() + + property var cfg_key: [] + + function toggle(name, checked) { + const index = cfg_key.indexOf(name); + + if (checked) { + if (index < 0) { + cfg_key.push(name); + } + } else if (index >= 0) { + cfg_key.splice(index, 1); + } + + configurationChanged(); + } + + Kirigami.FormLayout { + + Controls.CheckBox { + Kirigami.FormData.label: i18nc("@label show keyboard indicator when Caps Lock or Num Lock is activated", "Show when activated:") + readonly property string name: "Caps Lock" + checked: cfg_key.indexOf(name) >= 0 + text: i18nc("@option:check", "Caps Lock") + onToggled: root.toggle(name, checked) + } + + Controls.CheckBox { + readonly property string name: "Num Lock" + checked: cfg_key.indexOf(name) >= 0 + text: i18nc("@option:check", "Num Lock") + onToggled: root.toggle(name, checked) + } + } +} diff --git a/applets/keyboardindicator/contents/ui/main.qml b/applets/keyboardindicator/contents/ui/main.qml new file mode 100644 index 00000000..0ac930b2 --- /dev/null +++ b/applets/keyboardindicator/contents/ui/main.qml @@ -0,0 +1,101 @@ +/* + * SPDX-FileCopyrightText: 2018 Aleix Pol Gonzalez + * SPDX-FileCopyrightText: 2022 ivan tkachenko + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick + +import org.kde.plasma.plasmoid +import org.kde.plasma.core as PlasmaCore +import org.kde.kirigami as Kirigami +import org.kde.plasma.private.keyboardindicator as KeyboardIndicator +import org.kde.plasma.components as PlasmaComponents +import org.kde.plasma.extras as PlasmaExtras + +PlasmoidItem { + id: root + + readonly property KeyboardIndicator.KeyState capsLockState: Plasmoid.configuration.key.includes("Caps Lock") ? kiComponent.createObject(null, {key: Qt.Key_CapsLock}) : null + readonly property KeyboardIndicator.KeyState numLockState: Plasmoid.configuration.key.includes("Num Lock") ? kiComponent.createObject(null, {key: Qt.Key_NumLock}) : null + + Plasmoid.icon: { + if (capsLockState?.locked && numLockState?.locked) { + return "input-combo-on"; + } else if (capsLockState?.locked) { + return "input-caps-on"; + } else if (numLockState?.locked) { + return "input-num-on"; + } else { + return "input-caps-on"; + } + } + + Component { + id: kiComponent + KeyboardIndicator.KeyState { } + } + + // Only exists because the default CompactRepresentation doesn't expose a + // way to mark its icon as disabled. + // TODO remove once it gains that feature. + compactRepresentation: MouseArea { + id: compactMouse + + activeFocusOnTab: true + hoverEnabled: true + + Accessible.name: Plasmoid.title + Accessible.description: root.toolTipSubText + Accessible.role: Accessible.Button + + property bool wasExpanded: false + onPressed: wasExpanded = root.expanded + onClicked: root.expanded = !wasExpanded + + Kirigami.Icon { + anchors.fill: parent + source: Plasmoid.icon + active: compactMouse.containsMouse + enabled: root.capsLockState?.locked || root.numLockState?.locked || false + } + } + + fullRepresentation: PlasmaComponents.Page { + implicitWidth: Kirigami.Units.gridUnit * 12 + implicitHeight: Kirigami.Units.gridUnit * 12 + + PlasmaExtras.PlaceholderMessage { + anchors.centerIn: parent + width: parent.width - (Kirigami.Units.gridUnit * 4) + iconName: Plasmoid.icon + text: root.toolTipSubText + } + } + + switchWidth: Kirigami.Units.gridUnit * 12 + switchHeight: Kirigami.Units.gridUnit * 12 + + Plasmoid.status: root.capsLockState?.locked || root.numLockState?.locked + ? PlasmaCore.Types.ActiveStatus + : PlasmaCore.Types.HiddenStatus + + toolTipSubText: { + let text = []; + if (root.capsLockState?.locked) { + text.push(i18n("Caps Lock activated")); + } + if (root.numLockState?.locked) { + text.push(i18n("Num Lock activated")); + } + if (text.length > 0) { + // Not using KUIT markup for these newline characters because those + // get translated into HTML, and this text is displayed in the applet's + // tooltip which does not render HTML at all for security reasons + return text.join("\n"); + } else { + return i18n("No lock keys activated"); + } + } +} diff --git a/applets/keyboardindicator/metadata.json b/applets/keyboardindicator/metadata.json new file mode 100644 index 00000000..2090841e --- /dev/null +++ b/applets/keyboardindicator/metadata.json @@ -0,0 +1,151 @@ +{ + "KPackageStructure": "Plasma/Applet", + "KPlugin": { + "Authors": [ + { + "Email": "aleixpol@kde.org", + "Name": "Aleix Pol Gonzalez", + "Name[ar]": "أليكس بول غونزاليس", + "Name[ast]": "Aleix Pol i Gonzàlez", + "Name[az]": "Aleix Pol Gonzalez", + "Name[bg]": "Aleix Pol Gonzalez", + "Name[ca@valencia]": "Aleix Pol Gonzalez", + "Name[ca]": "Aleix Pol Gonzalez", + "Name[cs]": "Aleix Pol Gonzalez", + "Name[da]": "Aleix Pol Gonzalez", + "Name[de]": "Aleix Pol Gonzalez", + "Name[en_GB]": "Aleix Pol Gonzalez", + "Name[eo]": "Aleix Pol Gonzalez", + "Name[es]": "Aleix Pol Gonzalez", + "Name[eu]": "Aleix Pol Gonzalez", + "Name[fi]": "Aleix Pol Gonzalez", + "Name[fr]": "Aleix Pol Gonzalez", + "Name[gl]": "Aleix Pol Gonzalez", + "Name[he]": "אלש פול גונזלז", + "Name[hu]": "Aleix Pol Gonzalez", + "Name[ia]": "Aleix Pol Gonzalez", + "Name[id]": "Aleix Pol Gonzalez", + "Name[is]": "Aleix Pol Gonzalez", + "Name[it]": "Aleix Pol Gonzalez", + "Name[ja]": "Aleix Pol Gonzalez", + "Name[ka]": "Aleix Pol Gonzalez", + "Name[ko]": "Aleix Pol Gonzalez", + "Name[lt]": "Aleix Pol Gonzalez", + "Name[lv]": "Aleix Pol Gonzalez", + "Name[nl]": "Aleix Pol Gonzalez", + "Name[nn]": "Aleix Pol Gonzalez", + "Name[pl]": "Aleix Pol Gonzalez", + "Name[pt]": "Aleix Pol Gonzalez", + "Name[pt_BR]": "Aleix Pol Gonzalez", + "Name[ro]": "Aleix Pol Gonzalez", + "Name[ru]": "Aleix Pol Gonzalez", + "Name[sk]": "Aleix Pol Gonzalez", + "Name[sl]": "Aleix Pol Gonzalez", + "Name[sv]": "Aleix Pol Gonzalez", + "Name[tr]": "Aleix Pol Gonzalez", + "Name[uk]": "Aleix Pol Gonzalez", + "Name[vi]": "Aleix Pol Gonzalez", + "Name[x-test]": "xxAleix Pol Gonzalezxx", + "Name[zh_CN]": "Aleix Pol Gonzalez", + "Name[zh_TW]": "Aleix Pol Gonzalez" + } + ], + "BugReportUrl": "https://bugs.kde.org/enter_bug.cgi?product=kdeplasma-addons&component=Keyboard Indicator", + "Description": "Shows whether Caps Lock or Num Lock are active", + "Description[ar]": "تعرض إذا ما كان قفل الأرقام أو الحروف الكبيرة مفعل", + "Description[az]": "Caps Lock və ya Num Lock'un aktivliyini göstərir", + "Description[bg]": "Показва дали са активни Caps Lock или Num Lock", + "Description[ca@valencia]": "Mostra si es marca el bloqueig de «Bloq Maj» o «Bloq Núm»", + "Description[ca]": "Mostra si està actiu el bloqueig de «Bloq Maj» o «Bloq Núm»", + "Description[cs]": "Zobrazit, zda je CapsLock nebo NumLock aktivní", + "Description[da]": "Viser hvorvidt caps lock eller num lock er aktiveret", + "Description[de]": "Zeigt an, ob die Feststelltaste oder Nummernblocksperre aktiv sind", + "Description[en_GB]": "Shows whether Caps Lock or Num Lock are active", + "Description[eo]": "Montras ĉu Majŝlosilo aŭ Numŝlosilo estas aktivaj", + "Description[es]": "Muestra si BloqNum o BloqMayúsculas están activadas", + "Description[eu]": "«Blok Maius» edo «Blok Zk» aktibatu diren erakusten du", + "Description[fi]": "Näyttää, ovatko vaihto- ja numerolukko käytössä", + "Description[fr]": "Affiche l'état du verrouillage des majuscules et du pavé numérique", + "Description[gl]": "Amosa se as teclas de bloquear maiúsculas e de bloqueo numérico están activas.", + "Description[he]": "מציג האם Caps Lock או Num Lock פעילים", + "Description[hu]": "Megmutatja, hogy a Caps Lock vagy a Num Lock aktív-e", + "Description[ia]": "Monstra si clave de Caps Lock o Num Lock es active", + "Description[id]": "Menunjukkan apakah Caps Lock atau Num Lock aktif", + "Description[is]": "Sýnir hvort læsilyklar eins og Caps Lock eða Num Lock séu virkir", + "Description[it]": "Mostra se Bloc Maiusc o Bloc Num sono attivi", + "Description[ja]": "Caps Lock または Num Lock が有効かどうか表示します", + "Description[ka]": "CapsLock-ის და NumLock-ის ჩართულობის ჩვენება", + "Description[ko]": "Caps Lock이나 Num Lock 키 활성화 여부 표시", + "Description[lt]": "Rodyti ar įjungtos didžiosios raidės ar skaitmeninė klaviatūra", + "Description[lv]": "Parāda, vai ir ieslēgts „Caps Lock“ vai „Num Lock“ taustiņš", + "Description[nl]": "Toont of Caps Lock of Num Lock actief zijn", + "Description[nn]": "Vis om «Caps Lock» og «Num Lock» er på av eller av", + "Description[pl]": "Pokazuje czy Caps Lock oraz Num Lock są włączone", + "Description[pt]": "Mostra se o Caps Lock ou o Num Lock estão activos", + "Description[pt_BR]": "Mostra quando o Caps Lock ou Num Lock estão ativos", + "Description[ro]": "Arată dacă Caps Lock sau Num Lock sunt active", + "Description[ru]": "Отображение состояние режимов Caps Lock и Num Lock", + "Description[sk]": "Zobrazuje, či sú aktívne klávesy Caps Lock alebo Num Lock", + "Description[sl]": "Pokaži ali je aktivna tipka Caps lock ali Num lock", + "Description[sv]": "Visar om Caps Lock eller Num Lock är aktiva", + "Description[tr]": "Büyük Harf veya Sayı Kilidi’nin etkin olup olmadığını gösterir", + "Description[uk]": "Показує, чи задіяно Caps Lock або Num Lock", + "Description[vi]": "Cho biết Caps Lock hay Num Lock có đang hoạt động không", + "Description[x-test]": "xxShows whether Caps Lock or Num Lock are activexx", + "Description[zh_CN]": "显示大写或小键盘数字锁定键的激活状态", + "Description[zh_TW]": "顯示大寫鎖定 (Caps Lock) 或數字鎖定 (Num Lock) 是否開啟", + "EnabledByDefault": true, + "Icon": "input-caps-on", + "Id": "org.kde.plasma.keyboardindicator", + "License": "GPL-2.0+", + "Name": "Lock Keys Status", + "Name[ar]": "حالة قفل المفاتيح", + "Name[ast]": "Estáu de les tecles de bloquéu", + "Name[az]": "Düymələrin vəziyyətini kilidləyin", + "Name[bg]": "Статус на заключващите клавиши", + "Name[ca@valencia]": "Estat de les tecles de bloqueig", + "Name[ca]": "Estat de les tecles de bloqueig", + "Name[cs]": "Uzamknout stav kláves", + "Name[da]": "Status på låsetaster", + "Name[de]": "Sperrtastenstatus", + "Name[en_GB]": "Lock Keys Status", + "Name[eo]": "Klavoŝlosa Statuso", + "Name[es]": "Estado de las teclas de bloqueo", + "Name[eu]": "Giltzatzeko teklen egoera", + "Name[fi]": "Lukitusnäppäinten tila", + "Name[fr]": "État des touches de verrouillage", + "Name[gl]": "Estado das teclas bloqueantes", + "Name[he]": "מצב מקשי נעילה", + "Name[hu]": "Lock-billentyű jelző", + "Name[ia]": "Status de claves de blocar", + "Name[id]": "Status Tombol Kunci", + "Name[is]": "Staða læsilykla", + "Name[it]": "Stato della permanenza dei tasti", + "Name[ja]": "ロックキーの状態", + "Name[ka]": "დაბლოკვის გასაღების სტატუსი", + "Name[ko]": "잠금 키 상태", + "Name[lt]": "Užrakto klavišų būsena", + "Name[lv]": "Slēgu taustiņu statuss", + "Name[nl]": "Status van vergrendeltoetsen", + "Name[nn]": "Låsetast-status", + "Name[pl]": "Stan zablokowania klawiszy", + "Name[pt]": "Estado das Teclas Bloqueantes", + "Name[pt_BR]": "Status das teclas de bloqueio", + "Name[ro]": "Starea blocării tastelor", + "Name[ru]": "Состояние фиксации режимов клавиш", + "Name[sk]": "Stav zámku klávesov", + "Name[sl]": "Lock Keys Status", + "Name[sv]": "Status för låstangenter", + "Name[tr]": "Kilit Düğmeleri Durumu", + "Name[uk]": "Стан клавіш блокування", + "Name[vi]": "Trạng thái các phím khoá", + "Name[x-test]": "xxLock Keys Statusxx", + "Name[zh_CN]": "锁定键状态", + "Name[zh_TW]": "鎖定鍵狀態", + "Website": "https://kde.org/plasma-desktop/" + }, + "Keywords": "CapsLock;NumLock;", + "Keywords[x-test]": "xxCapsLockxx;xxNumLockxx;", + "X-Plasma-API-Minimum-Version": "6.0", + "X-Plasma-NotificationAreaCategory": "Hardware" +} diff --git a/applets/kickerdash/metadata.json b/applets/kickerdash/metadata.json new file mode 100644 index 00000000..ce148425 --- /dev/null +++ b/applets/kickerdash/metadata.json @@ -0,0 +1,152 @@ +{ + "KPackageStructure": "Plasma/Applet", + "KPlugin": { + "Authors": [ + { + "Email": "hein@kde.org", + "Name": "Eike Hein", + "Name[ar]": "إيكي هين", + "Name[az]": "Eike Hein", + "Name[bg]": "Eike Hein", + "Name[ca@valencia]": "Eike Hein", + "Name[ca]": "Eike Hein", + "Name[cs]": "Eike Hein", + "Name[da]": "Eike Hein", + "Name[de]": "Eike Hein", + "Name[en_GB]": "Eike Hein", + "Name[eo]": "Eike Hein", + "Name[es]": "Eike Hein", + "Name[eu]": "Eike Hein", + "Name[fi]": "Eike Hein", + "Name[fr]": "Eike Hein", + "Name[gl]": "Eike Hein", + "Name[he]": "אייק היין", + "Name[hu]": "Eike Hein", + "Name[ia]": "Eike Hein", + "Name[id]": "Eike Hein", + "Name[is]": "Eike Hein", + "Name[it]": "Eike Hein", + "Name[ja]": "Eike Hein", + "Name[ka]": "აიკე ჰაინი", + "Name[ko]": "Eike Hein", + "Name[lt]": "Eike Hein", + "Name[lv]": "Eike Hein", + "Name[nl]": "Eike Hein", + "Name[nn]": "Eike Hein", + "Name[pl]": "Eike Hein", + "Name[pt]": "Eike Hein", + "Name[pt_BR]": "Eike Hein", + "Name[ro]": "Eike Hein", + "Name[ru]": "Eike Hein", + "Name[sk]": "Eike Hein", + "Name[sl]": "Eike Hein", + "Name[sv]": "Eike Hein", + "Name[tr]": "Eike Hein", + "Name[uk]": "Eike Hein", + "Name[vi]": "Eike Hein", + "Name[x-test]": "xxEike Heinxx", + "Name[zh_CN]": "Eike Hein", + "Name[zh_TW]": "Eike Hein" + } + ], + "Category": "Application Launchers", + "Description": "A fullscreen application launcher", + "Description[ar]": "مطلق تطبيقات ملء الشاشة ", + "Description[az]": "Tam ekran tətbiq başladıcısı", + "Description[bg]": "Стартер на приложения на цял екран", + "Description[ca@valencia]": "Un iniciador d'aplicacions a pantalla completa", + "Description[ca]": "Un llançador d'aplicacions a pantalla completa", + "Description[cs]": "Spouštěč aplikací na celou obrazovku", + "Description[da]": "En fuldskærms programstarter", + "Description[de]": "Anwendungsstarter im Vollbildmodus", + "Description[en_GB]": "A fullscreen application launcher", + "Description[eo]": "Plenekrana aplikaĵlanĉilo", + "Description[es]": "Lanzador de aplicaciones a pantalla completa", + "Description[eu]": "Pantaila-osoko aplikazio abiarazle bat", + "Description[fi]": "Koko näytön sovelluskäynnistin", + "Description[fr]": "Un lanceur d'applications en plein écran", + "Description[gl]": "Un iniciador de aplicacións a pantalla completa", + "Description[he]": "משגר יישומים במסך מלא", + "Description[hu]": "Teljes képernyős alkalmazásindító", + "Description[ia]": "Un lanceator de application a schermo plen", + "Description[id]": "Sebuah peluncur aplikasi layar penuh", + "Description[is]": "Forritaræsir sem fyllir skjáinn", + "Description[it]": "Un lanciatore di applicazioni a schermo intero", + "Description[ja]": "フルスクリーンのアプリケーションランチャー", + "Description[ka]": "აპლიკაციების მთელ ეკრანზე გამშვები", + "Description[ko]": "전체 화면 앱 실행기", + "Description[lt]": "Viso ekrano programų paleidyklė", + "Description[lv]": "Pilnekrāna programmu palaidējs", + "Description[nl]": "Een programmastarter met volledig scherm", + "Description[nn]": "Fullskjerms programstartar", + "Description[pl]": "Pełnoekranowe menu programów", + "Description[pt]": "Um lançador de aplicações em ecrã completo", + "Description[pt_BR]": "Lançador de aplicativos em tela inteira", + "Description[ro]": "Lansator de aplicații pe tot ecranul", + "Description[ru]": "Полноэкранное меню запуска приложений", + "Description[sk]": "Spúšťač aplikácií na celú obrazovku", + "Description[sl]": "Celozaslonski zaganjalnik aplikacij", + "Description[sv]": "Ett fullskärmsverktyg för programstart", + "Description[tr]": "Tam ekran uygulama başlatıcısı", + "Description[uk]": "Повноекранний засіб запуску програм", + "Description[vi]": "Trình khởi chạy ứng dụng, với kích cỡ toàn màn hình", + "Description[x-test]": "xxA fullscreen application launcherxx", + "Description[zh_CN]": "全屏应用程序启动器", + "Description[zh_TW]": "一個全螢幕應用程式啟動器", + "FormFactors": [ + "desktop" + ], + "Icon": "start-here-kde", + "Id": "org.kde.plasma.kickerdash", + "License": "GPL-2.0+", + "Name": "Application Dashboard", + "Name[ar]": "لوحة معلومات التطبيق", + "Name[az]": "Tətbiq tablosu", + "Name[bg]": "Панел на приложения", + "Name[ca@valencia]": "Tauler d'aplicacions", + "Name[ca]": "Tauler d'aplicacions", + "Name[cs]": "Pracovní plocha aplikace", + "Name[da]": "Programoverblik", + "Name[de]": "Anwendungsübersicht", + "Name[en_GB]": "Application Dashboard", + "Name[eo]": "Aplika Instrumentpanelo", + "Name[es]": "Tablero de aplicaciones", + "Name[eu]": "Aplikazioen Aginte-mahaia", + "Name[fi]": "Sovelluskojelauta", + "Name[fr]": "Tableau de bord des applications", + "Name[gl]": "Cadro de control de aplicacións", + "Name[he]": "לוח מחווני יישומים", + "Name[hu]": "Alkalmazás irányítópult", + "Name[ia]": "Pannello de instrumentos de Application", + "Name[id]": "Dasbor Aplikasi", + "Name[is]": "Hugbúnaðarborð", + "Name[it]": "Cruscotto applicazioni", + "Name[ja]": "アプリケーションダッシュボード", + "Name[ka]": "აპლიკაციის სამუშაო დაფა", + "Name[ko]": "앱 대시보드", + "Name[lt]": "Programų skydelis", + "Name[lv]": "Programmu panelis", + "Name[nl]": "Toepassingendashboard", + "Name[nn]": "Program­kontrollpult", + "Name[pl]": "Tablica programów", + "Name[pt]": "Quadro da Aplicação", + "Name[pt_BR]": "Painel de aplicativos", + "Name[ro]": "Panou de bord pentru aplicații", + "Name[ru]": "Доска приложений", + "Name[sk]": "Hlavný panel aplikácií", + "Name[sl]": "Application Dashboard", + "Name[sv]": "Instrumentpanel för program", + "Name[tr]": "Uygulama Gösterge Tablosu", + "Name[uk]": "Панель приладів програм", + "Name[vi]": "Bảng điều khiển ứng dụng", + "Name[x-test]": "xxApplication Dashboardxx", + "Name[zh_CN]": "应用程序面板", + "Name[zh_TW]": "應用程式儀表板", + "Website": "https://kde.org/plasma-desktop" + }, + "X-Plasma-API-Minimum-Version": "6.0", + "X-Plasma-Provides": [ + "org.kde.plasma.launchermenu" + ], + "X-Plasma-RootPath": "org.kde.plasma.kicker" +} diff --git a/applets/konsoleprofiles/Messages.sh b/applets/konsoleprofiles/Messages.sh new file mode 100644 index 00000000..8ffd89eb --- /dev/null +++ b/applets/konsoleprofiles/Messages.sh @@ -0,0 +1,2 @@ +#! /usr/bin/env bash +$XGETTEXT `find . -name \*.qml` -o $podir/plasma_applet_org.kde.plasma.konsoleprofiles.pot diff --git a/applets/konsoleprofiles/package/contents/ui/main.qml b/applets/konsoleprofiles/package/contents/ui/main.qml new file mode 100644 index 00000000..9a37d998 --- /dev/null +++ b/applets/konsoleprofiles/package/contents/ui/main.qml @@ -0,0 +1,141 @@ +/* + * SPDX-FileCopyrightText: 2011, 2012 Shaun Reich + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.0 +import QtQuick.Layouts 1.1 +import QtQuick.Controls +import org.kde.plasma.plasmoid 2.0 +import org.kde.plasma.core as PlasmaCore +import org.kde.kirigami 2.20 as Kirigami +import org.kde.ksvg 1.0 as KSvg +import org.kde.kitemmodels 1.0 as KItemModels +import org.kde.plasma.components 3.0 as PlasmaComponents3 +import org.kde.plasma.extras 2.0 as PlasmaExtras +import org.kde.plasma.private.profiles 1.0 as Profiles + +PlasmoidItem { + id: konsoleProfiles + + readonly property bool inPanel: (Plasmoid.location === PlasmaCore.Types.TopEdge + || Plasmoid.location === PlasmaCore.Types.RightEdge + || Plasmoid.location === PlasmaCore.Types.BottomEdge + || Plasmoid.location === PlasmaCore.Types.LeftEdge) + + switchWidth: Kirigami.Units.gridUnit * 11 + switchHeight: Kirigami.Units.gridUnit * 9 + + Layout.minimumWidth: Kirigami.Units.gridUnit * 12 + Layout.minimumHeight: Kirigami.Units.gridUnit * 10 + + Plasmoid.icon: inPanel ? "dialog-scripts-symbolic" : "utilities-terminal" + + fullRepresentation: FocusScope { + anchors.fill: parent + + Connections { + target: konsoleProfiles + function onExpandedChanged() { + if (konsoleProfiles.expanded) { + view.forceActiveFocus(); + } + } + } + + KItemModels.KSortFilterProxyModel { + id: sortModel + sortRoleName: "name" + sortOrder: Qt.AscendingOrder + sourceModel: Profiles.ProfilesModel { + id: profilesModel + appName: "konsole" + } + } + + Row { + id: headerRow + anchors { left: parent.left; right: parent.right } + + Kirigami.Icon { + id: appIcon + source: "utilities-terminal" + width: Kirigami.Units.iconSizes.medium + height: Kirigami.Units.iconSizes.medium + } + + PlasmaComponents3.Label { + id: header + text: i18nc("@title", "Konsole Profiles") + textFormat: Text.PlainText + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + width: parent.width - appIcon.width * 2 + height: parent.height + } + } + + KSvg.SvgItem { + id: separator + + anchors { left: headerRow.left; right: headerRow.right; top: headerRow.bottom } + imagePath: "widgets/line" + elementId: "horizontal-line" + } + + Text { + id: textMetric + visible: false + // translated but not used, we just need length/height + text: i18n("Arbitrary String Which Says Something") + } + + ScrollView { + anchors { left: parent.left; right: parent.right; bottom: parent.bottom; top: separator.bottom; topMargin: Kirigami.Units.smallSpacing} + + ListView { + id: view + + model: sortModel + clip: true + focus: true + keyNavigationWraps: true + + delegate: PlasmaComponents3.ItemDelegate { + id: listdelegate + + width: ListView.view.width + height: textMetric.paintedHeight * 2 + + hoverEnabled: true + text: model.name + + Accessible.role: Accessible.Button + + onClicked: { + openProfile(); + } + + onHoveredChanged: { + if (hovered) { + view.currentIndex = index; + } + } + + function openProfile() { + /*var service = profilesSource.serviceForSource(model["DataEngineSource"]) + var operation = service.operationDescription("open") + var = service.startOperationCall(operation)*/ + console.error(model.profileIdentifier) + profilesModel.openProfile(model.profileIdentifier) + } + } + + highlight: PlasmaExtras.Highlight {} + + highlightMoveDuration: Kirigami.Units.longDuration + highlightMoveVelocity: 1 + } + } + } +} diff --git a/applets/konsoleprofiles/package/metadata.json b/applets/konsoleprofiles/package/metadata.json new file mode 100644 index 00000000..8c30f0b5 --- /dev/null +++ b/applets/konsoleprofiles/package/metadata.json @@ -0,0 +1,149 @@ +{ + "KPackageStructure": "Plasma/Applet", + "KPlugin": { + "Authors": [ + { + "Email": "shaun.reich@kdemail.net", + "Name": "Shaun Reich", + "Name[ar]": "شون رايش", + "Name[az]": "Shaun Reich", + "Name[bg]": "Shaun Reich", + "Name[ca@valencia]": "Shaun Reich", + "Name[ca]": "Shaun Reich", + "Name[cs]": "Shaun Reich", + "Name[da]": "Shaun Reich ", + "Name[de]": "Shaun Reich", + "Name[en_GB]": "Shaun Reich", + "Name[eo]": "Shaun Reich", + "Name[es]": "Shaun Reich", + "Name[eu]": "Shaun Reich", + "Name[fi]": "Shaun Reich", + "Name[fr]": "Shaun Reich", + "Name[gl]": "Shaun Reich", + "Name[he]": "שון רייך", + "Name[hu]": "Shaun Reich", + "Name[ia]": "Shaun Reich", + "Name[id]": "Shaun Reich", + "Name[is]": "Shaun Reich", + "Name[it]": "Shaun Reich", + "Name[ja]": "Shaun Reich", + "Name[ka]": "Shaun Reich", + "Name[ko]": "Shaun Reich", + "Name[lt]": "Shaun Reich", + "Name[lv]": "Shaun Reich", + "Name[nl]": "Shaun Reich", + "Name[nn]": "Shaun Reich", + "Name[pl]": "Shaun Reich", + "Name[pt]": "Shaun Reich", + "Name[pt_BR]": "Shaun Reich", + "Name[ro]": "Shaun Reich", + "Name[ru]": "Shaun Reich", + "Name[sk]": "Shaun Reich", + "Name[sl]": "Shaun Reich", + "Name[sv]": "Shaun Reich", + "Name[tr]": "Shaun Reich", + "Name[uk]": "Shaun Reich", + "Name[vi]": "Shaun Reich", + "Name[x-test]": "xxShaun Reichxx", + "Name[zh_CN]": "Shaun Reich", + "Name[zh_TW]": "Shaun Reich" + } + ], + "BugReportUrl": "https://bugs.kde.org/enter_bug.cgi?product=kdeplasma-addons&component=konsoleprofiles", + "Category": "Utilities", + "Description": "List and launch Konsole profiles", + "Description[ar]": "تعرض و تطلق تشكيلات كونسول", + "Description[az]": "Konsol profillərini sıralamaq və başlatmaq", + "Description[bg]": "Показване и избиране на профили на Konsole", + "Description[ca@valencia]": "Llista i inicia els perfils de Konsole", + "Description[ca]": "Llista i llança els perfils del Konsole", + "Description[cs]": "Seznam a spouštění profilů Konsole", + "Description[da]": "List og start Konsole-profiler", + "Description[de]": "Konsole-Profile anzeigen und starten", + "Description[en_GB]": "List and launch Konsole profiles", + "Description[eo]": "Listigi kaj lanĉi Konsole-profilojn", + "Description[es]": "Listar y lanzar perfiles de Konsole", + "Description[eu]": "«Konsole»ren profilak zerrendatu eta jaurtitzea", + "Description[fi]": "Luettele ja käynnistä Konsole-profiileja", + "Description[fr]": "Lister et lancer des profils de Konsole", + "Description[gl]": "Enumera e inicia perfís de Konsole", + "Description[he]": "הצגת והפעלת פרופילים של Konsole", + "Description[hu]": "Konsole profilok listázása és indítása", + "Description[ia]": "Lista e lancea profilos de Konsole", + "Description[id]": "Cantumkan dan luncurkan profil Konsole", + "Description[is]": "Telur upp og keyrir Konsole-prófíla", + "Description[it]": "Elenca ed esegue i profili di Konsole", + "Description[ja]": "Konsole のプロファイルを起動します", + "Description[ka]": "Konsole-ის პროფილების პოვნა და გაშვება", + "Description[ko]": "Konsole 프로필을 보고 실행하기", + "Description[lt]": "Išvardyti ir paleisti Konsole profilius", + "Description[lv]": "Uzskaita un palaiž „Konsole“ profilus", + "Description[nl]": "Toon en start Konsole-profielen", + "Description[nn]": "Vis og start Konsoll-profilar", + "Description[pl]": "Wypisuje i uruchamia profile Konsoli", + "Description[pt]": "Listar e invocar os perfis do Konsole", + "Description[pt_BR]": "Lista e carrega os perfis do Konsole", + "Description[ro]": "Enumeră și lansează profiluri de Konsolă", + "Description[ru]": "Запуск Konsole для разных задач", + "Description[sk]": "Zoznam a spustenie profilov Konsole", + "Description[sl]": "Zlistaj in zaženi profile Konzole", + "Description[sv]": "Lista och starta terminalprofiler", + "Description[tr]": "Konsole profillerini listele ve çalıştır", + "Description[uk]": "Перегляньте і запустіть профілі Konsole", + "Description[vi]": "Liệt kê và khởi chạy các hồ sơ Konsole", + "Description[x-test]": "xxList and launch Konsole profilesxx", + "Description[zh_CN]": "列出并启动 Konsole 配置方案", + "Description[zh_TW]": "列出並啟動 Konsole 設定檔", + "FormFactors": [ + "desktop" + ], + "Icon": "utilities-terminal", + "Id": "org.kde.plasma.konsoleprofiles", + "License": "GPL-2.0+", + "Name": "Konsole Profiles", + "Name[ar]": "تشكيلات كونسول", + "Name[az]": "Konsol profilləri", + "Name[bg]": "Профили на Konsole", + "Name[ca@valencia]": "Perfils de Konsole", + "Name[ca]": "Perfils del Konsole", + "Name[cs]": "Profily Konsole", + "Name[da]": "Konsole-profiler", + "Name[de]": "Konsole-Profile", + "Name[en_GB]": "Konsole Profiles", + "Name[eo]": "Konsole Profiloj", + "Name[es]": "Perfiles de Konsole", + "Name[eu]": "«Konsole»ren profilak", + "Name[fi]": "Konsole-profiilit", + "Name[fr]": "Profils de Konsole", + "Name[gl]": "Perfís de Konsole", + "Name[he]": "פרופילים של Konsole", + "Name[hu]": "Konsole profilok", + "Name[ia]": "Profilos de Konsole", + "Name[id]": "Profil Konsole", + "Name[is]": "Konsole-prófílar", + "Name[it]": "Profili di Konsole", + "Name[ja]": "Konsole プロファイル", + "Name[ka]": "Konsole -ის პროფილები", + "Name[ko]": "Konsole 프로필", + "Name[lt]": "Konsole profiliai", + "Name[lv]": "„Konsole“ profili", + "Name[nl]": "Konsole-profielen", + "Name[nn]": "Konsoll-profilar", + "Name[pl]": "Profile Konsoli", + "Name[pt]": "Perfis do Konsole", + "Name[pt_BR]": "Perfis do Konsole", + "Name[ro]": "Profiluri Konsolă", + "Name[ru]": "Konsole: профили", + "Name[sk]": "Profily Konsole", + "Name[sl]": "Profili Konzole", + "Name[sv]": "Terminalprofiler", + "Name[tr]": "Konsol Profilleri", + "Name[uk]": "Профілі Konsole", + "Name[vi]": "Hồ sơ Konsole", + "Name[x-test]": "xxKonsole Profilesxx", + "Name[zh_CN]": "Konsole 配置方案", + "Name[zh_TW]": "Konsole 設定檔", + "Website": "https://userbase.kde.org/Plasma" + }, + "X-Plasma-API-Minimum-Version": "6.0" +} diff --git a/applets/mediaframe/CMakeLists.txt b/applets/mediaframe/CMakeLists.txt new file mode 100644 index 00000000..fd1a7bad --- /dev/null +++ b/applets/mediaframe/CMakeLists.txt @@ -0,0 +1,15 @@ +plasma_install_package(package org.kde.plasma.mediaframe) + +ecm_add_qml_module(mediaframeplugin URI org.kde.plasma.private.mediaframe) +target_sources(mediaframeplugin PRIVATE + plugin/mediaframe.cpp + plugin/mediaframeplugin.cpp +) +target_link_libraries(mediaframeplugin PRIVATE + Qt::Core + Qt::Qml + Qt::Quick + KF6::I18n + KF6::KIOCore +) +ecm_finalize_qml_module(mediaframeplugin) diff --git a/applets/mediaframe/Messages.sh b/applets/mediaframe/Messages.sh new file mode 100644 index 00000000..6d9d642f --- /dev/null +++ b/applets/mediaframe/Messages.sh @@ -0,0 +1,4 @@ +#! /usr/bin/env bash +$EXTRACTRC `find . -name \*.rc -o -name \*.ui -o -name \*.kcfg` >> rc.cpp +$XGETTEXT `find . -name \*.js -o -name \*.qml -o -name \*.cpp` -o $podir/plasma_applet_org.kde.plasma.mediaframe.pot +rm -f rc.cpp diff --git a/applets/mediaframe/package/contents/config/config.qml b/applets/mediaframe/package/contents/config/config.qml new file mode 100644 index 00000000..d7a818d1 --- /dev/null +++ b/applets/mediaframe/package/contents/config/config.qml @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2015 Lars Pontoppidan + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.1 + +import org.kde.plasma.configuration 2.0 + +ConfigModel { + ConfigCategory { + name: i18nc("@title", "General") + icon: "image" + source: "ConfigGeneral.qml" + } + ConfigCategory { + name: i18nc("@title", "Paths") + icon: "folder" + source: "ConfigPaths.qml" + } +} diff --git a/applets/mediaframe/package/contents/config/main.xml b/applets/mediaframe/package/contents/config/main.xml new file mode 100644 index 00000000..8a5d0039 --- /dev/null +++ b/applets/mediaframe/package/contents/config/main.xml @@ -0,0 +1,32 @@ + + + + + + 10.0 + + + true + + + true + + + true + + + true + + + 1 + + + + + + + + diff --git a/applets/mediaframe/package/contents/ui/ConfigGeneral.qml b/applets/mediaframe/package/contents/ui/ConfigGeneral.qml new file mode 100644 index 00000000..678995ac --- /dev/null +++ b/applets/mediaframe/package/contents/ui/ConfigGeneral.qml @@ -0,0 +1,200 @@ +/* + * SPDX-FileCopyrightText: 2015 Lars Pontoppidan + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import org.kde.kirigami 2.4 as Kirigami +import org.kde.kcmutils as KCM + +KCM.SimpleKCM { + id: root + + property alias cfg_randomize: randomizeCheckBox.checked + property alias cfg_pauseOnMouseOver: pauseOnMouseOverCheckBox.checked + property alias cfg_leftClickOpenImage: leftClickOpenImageCheckBox.checked + //property alias cfg_showCountdown: showCountdownCheckBox.checked + property alias cfg_fillMode: root.fillMode + + property int cfg_interval: 0 + property int hoursIntervalValue: Math.floor(cfg_interval / 3600) + property int minutesIntervalValue: Math.floor(cfg_interval % 3600) / 60 + property int secondsIntervalValue: cfg_interval % 3600 % 60 + + /* + * Image.Stretch - the image is scaled to fit + * Image.PreserveAspectFit - the image is scaled uniformly to fit without cropping + * Image.PreserveAspectCrop - the image is scaled uniformly to fill, cropping if necessary + * Image.Tile - the image is duplicated horizontally and vertically + * Image.TileVertically - the image is stretched horizontally and tiled vertically + * Image.TileHorizontally - the image is stretched vertically and tiled horizontally + * Image.Pad - the image is not transformed + */ + property int fillMode: Image.PreserveAspectFit + + Kirigami.FormLayout { + TextMetrics { + id: textMetrics + text: "00" + } + + //FIXME: there should be only one spinbox: QtControls spinboxes are still too limited for it tough + RowLayout { + Layout.fillWidth: true + + Kirigami.FormData.label: i18n("Change picture every:") + + Connections { + target: root + function onHoursIntervalValueChanged() {hoursInterval.value = root.hoursIntervalValue} + function onMinutesIntervalValueChanged() {minutesInterval.value = root.minutesIntervalValue} + function onSecondsIntervalValueChanged() {secondsInterval.value = root.secondsIntervalValue} + } + SpinBox { + id: hoursInterval + value: root.hoursIntervalValue + from: 0 + to: 24 + editable: true + onValueChanged: cfg_interval = hoursInterval.value * 3600 + minutesInterval.value * 60 + secondsInterval.value + } + Label { + text: i18n("Hours") + } + Item { + Layout.preferredWidth: Kirigami.Units.gridUnit + } + SpinBox { + id: minutesInterval + value: root.minutesIntervalValue + from: 0 + to: 60 + editable: true + onValueChanged: cfg_interval = hoursInterval.value * 3600 + minutesInterval.value * 60 + secondsInterval.value + } + Label { + text: i18n("Minutes") + } + Item { + Layout.preferredWidth: Kirigami.Units.gridUnit + } + SpinBox { + id: secondsInterval + value: root.secondsIntervalValue + from: root.hoursIntervalValue === 0 && root.minutesIntervalValue === 0 ? 1 : 0 + to: 60 + editable: true + onValueChanged: cfg_interval = hoursInterval.value * 3600 + minutesInterval.value * 60 + secondsInterval.value + } + Label { + text: i18n("Seconds") + } + } + + Item { + Kirigami.FormData.isSection: false + } + + ComboBox { + id: comboBox + Kirigami.FormData.label: i18nc("@label:listbox", "Image fill mode:") + + // Layout.minimumWidth: Kirigami.Units.gridUnit * 10 + currentIndex: fillModeToIndex(fillMode) + textRole: "text" + model: [ + { + text: i18nc("@item:inlistbox", "Stretch"), + description: i18nc("@info", "The image is scaled to fit the frame"), + value: Image.Stretch + }, + { + text: i18nc("@item:inlistbox", "Preserve aspect fit"), + description: i18nc("@info", "The image is scaled uniformly to fit without cropping"), + value: Image.PreserveAspectFit + }, + { + text: i18nc("@item:inlistbox", "Preserve aspect crop"), + description: i18nc("@info", "The image is scaled uniformly to fill, cropping if necessary"), + value: Image.PreserveAspectCrop + }, + { + text: i18nc("@item:inlistbox", "Tile"), + description: i18nc("@info", "The image is duplicated horizontally and vertically"), + value: Image.Tile + }, + { + text: i18nc("@item:inlistbox", "Tile vertically"), + description: i18nc("@info", "The image is stretched horizontally and tiled vertically"), + value: Image.TileVertically + }, + { + text: i18nc("@item:inlistbox", "Tile horizontally"), + description: i18nc("@info", "The image is stretched vertically and tiled horizontally"), + value: Image.TileHorizontally + }, + { + text: i18nc("@item:inlistbox", "Pad"), + description: i18nc("@info", "The image is not transformed"), + value: Image.Pad + } + ] + + onActivated: root.fillMode = comboBox.model[index].value + + function fillModeToIndex(fillMode) { + if(fillMode == Image.Stretch) { + return 0 + } + else if(fillMode == Image.PreserveAspectFit) { + return 1 + } + else if(fillMode == Image.PreserveAspectCrop) { + return 2 + } + else if(fillMode == Image.Tile) { + return 3 + } + else if(fillMode == Image.TileVertically) { + return 4 + } + else if(fillMode == Image.TileHorizontally) { + return 5 + } + else if(fillMode == Image.Pad) { + return 6 + } + } + } + + Label { + id: fillModeDescription + Layout.fillWidth: true + wrapMode: Text.WordWrap + text: comboBox.model[comboBox.currentIndex] ? comboBox.model[comboBox.currentIndex].description : "" + } + + Item { + Kirigami.FormData.isSection: false + } + + CheckBox { + id: randomizeCheckBox + Kirigami.FormData.label: i18nc("@label:checkbox", "General:") + text: i18nc("@option:check", "Randomize order") + } + + CheckBox { + id: pauseOnMouseOverCheckBox + text: i18nc("@option:check", "Pause slideshow when cursor is over image") + } + + CheckBox { + id: leftClickOpenImageCheckBox + text: i18nc("@option:check", "Click on image to open in external application") + } + } +} diff --git a/applets/mediaframe/package/contents/ui/ConfigPaths.qml b/applets/mediaframe/package/contents/ui/ConfigPaths.qml new file mode 100644 index 00000000..377d2be4 --- /dev/null +++ b/applets/mediaframe/package/contents/ui/ConfigPaths.qml @@ -0,0 +1,134 @@ +/* + * SPDX-FileCopyrightText: 2015 Lars Pontoppidan + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtCore +import QtQuick +import QtQuick.Controls as QQC2 +import QtQuick.Dialogs +import QtQuick.Layouts + +import org.kde.plasma.plasmoid 2.0 +import org.kde.kirigami 2.5 as Kirigami +import org.kde.kcmutils as KCM + +KCM.ScrollViewKCM { + + signal configurationChanged + + property var cfg_pathList: [] + + function addPath(object) { + pathModel.append( object ) + cfg_pathList.push( JSON.stringify(object) ) + configurationChanged(); + } + + function removePath(index) { + if(pathModel.count > 0) { + pathModel.remove(index) + cfg_pathList.splice(index,1) + configurationChanged(); + } + } + + Component.onCompleted: { + // Load the list back in + var list = plasmoid.configuration.pathList + cfg_pathList = [] + for(var i in list) { + addPath( JSON.parse(list[i]) ) + } + } + + view: ListView { + id: pathsList + + model: ListModel { + id: pathModel + } + + delegate: Kirigami.SwipeListItem { + id: folderDelegate + + width: pathsList.width + + contentItem: QQC2.Label { + Layout.fillWidth: true + text: String(model.path).replace("file://", "") + textFormat: Text.PlainText + } + + actions: [ + Kirigami.Action { + icon.name: "list-remove" + tooltip: i18nd("plasma_wallpaper_org.kde.image", "Remove path") + onTriggered: removePath(model.index) + } + ] + } + + FileDialog { + id: fileDialog + + title: i18nc("@title:window", "Choose Files") + currentFolder: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0] + fileMode: FileDialog.OpenFiles + + // TODO get valid filter list from native code? + //nameFilters: [ "Image files (*.png *.jpg)", "All files (*)" ] + //selectedNameFilter: "All files (*)" + + onAccepted: { + console.log("Accepted: " + selectedFiles) + + for (var i = 0; i < selectedFiles.length; ++i) { + var item = { 'path':selectedFiles[i], 'type':'file' } + addPath(item) + } + } + + onRejected: { + console.log("Canceled") + } + } + + FolderDialog { + id: folderDialog + + visible: false + title: i18nc("@title:window", "Choose a Folder") + currentFolder: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0] + + onAccepted: { + console.log("Accepted: " + selectedFolder) + + var item = { 'path':selectedFolder, 'type':'folder' } + addPath(item) + } + + onRejected: { + console.log("Canceled") + } + } + } + + footer: RowLayout { + QQC2.Button { + icon.name: "folder-new" + onClicked: folderDialog.visible = true + text: i18nc("@action:button", "Add Folder…") + } + + QQC2.Button { + icon.name: "document-new" + onClicked: fileDialog.visible = true + text: i18nc("@action:button", "Add Files…") + } + Item { + Layout.fillWidth: true + } + } +} diff --git a/applets/mediaframe/package/contents/ui/main.qml b/applets/mediaframe/package/contents/ui/main.qml new file mode 100644 index 00000000..9e8a4ce8 --- /dev/null +++ b/applets/mediaframe/package/contents/ui/main.qml @@ -0,0 +1,457 @@ +/* + * SPDX-FileCopyrightText: 2015 Lars Pontoppidan + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick +import QtQuick.Layouts +import QtQuick.Dialogs + +import org.kde.draganddrop 2.0 as DragDrop + +import org.kde.plasma.plasmoid 2.0 +import org.kde.plasma.core as PlasmaCore +import org.kde.kirigami 2.20 as Kirigami +import org.kde.plasma.components 3.0 as PlasmaComponents3 +import org.kde.kquickcontrolsaddons 2.0 + +import org.kde.plasma.private.mediaframe 2.0 + +PlasmoidItem { + id: main + + MediaFrame { + id: items + random: plasmoid.configuration.randomize + } + + preferredRepresentation: fullRepresentation + + switchWidth: Kirigami.Units.gridUnit * 5 + switchHeight: Kirigami.Units.gridUnit * 5 + + Plasmoid.backgroundHints: PlasmaCore.Types.DefaultBackground | PlasmaCore.Types.ConfigurableBackground + + width: Kirigami.Units.gridUnit * 20 + height: Kirigami.Units.gridUnit * 13 + + property string activeSource: "" + property string transitionSource: "" + + readonly property bool pause: overlayMouseArea.containsMouse + + readonly property int itemCount: (items.count + items.futureLength) + readonly property bool hasItems: ((itemCount > 0) || (items.futureLength > 0)) + readonly property bool isTransitioning: faderAnimation.running + + onActiveSourceChanged: { + items.watch(activeSource) + } + + onHasItemsChanged: { + if(hasItems) { + if(activeSource == "") + nextItem() + } + } + + onExternalData: (mimetype, data) => { + var type = items.isDir(data) ? "folder" : "file"; + var item = { + "path": data, + "type": type + }; + + addItem(item); + } + + function loadPathList() { + var list = plasmoid.configuration.pathList + items.clear() + for(var i in list) { + var item = JSON.parse(list[i]) + items.add(item.path,true) + } + } + + Component.onCompleted: { + loadPathList() + + if (items.random) + nextItem() + } + + Connections { + target: plasmoid.configuration + function onPathListChanged() { + loadPathList() + } + } + + function addItem(item) { + + if(items.isAdded(item.path)) { + console.info(item.path,"already exists. Skipping…") + return + } + // work-around for QTBUG-67773: + // C++ object property of type QVariant(QStringList) is not updated on changes from QML + // so explicitly create a deep JSValue copy, modify that and then set it back to overwrite the old + var updatedList = plasmoid.configuration.pathList.slice(); + updatedList.push(JSON.stringify(item)); + plasmoid.configuration.pathList = updatedList; + } + + function nextItem() { + + if(!hasItems) { + console.warn("No items available") + return + } + + var active = activeSource + + // Only record history if we have more than one item + if(itemCount > 1) + items.pushHistory(active) + + if(items.futureLength > 0) { + setActiveSource(items.popFuture()) + } else { + //setLoading() + items.get(function(filePath){ + setActiveSource(filePath) + //unsetLoading() + },function(errorMessage){ + //unsetLoading() + console.error("Error while getting next image",errorMessage) + }) + } + } + + function previousItem() { + var active = activeSource + items.pushFuture(active) + var filePath = items.popHistory() + setActiveSource(filePath) + } + + Connections { + target: items + + function onItemChanged(path) { + console.log("item",path,"changed") + activeSource = "" + setActiveSource(path) + } + + } + + Timer { + id: nextTimer + interval: (plasmoid.configuration.interval*1000) + repeat: true + running: hasItems && !pause + onTriggered: nextItem() + } + + Item { + id: itemView + anchors.fill: parent + + /* + Video { + id: video + width : 800 + height : 600 + source: "" + + onStatusChanged: { + if(status == Video.Loaded) + video.play() + } + } + */ + + Item { + id: imageView + visible: hasItems + anchors.fill: parent + + // This timer prevents reloading the image too often when resizing, + // to minimize excessively re-reading the file on disk + Timer { + id: imageReloadTimer + interval: 250 + running: false + onTriggered: { + frontImage.sourceSize.width = width + frontImage.sourceSize.height = height + } + } + + Image { + id: bufferImage + + + anchors.fill: parent + fillMode: plasmoid.configuration.fillMode + + opacity: 0 + + cache: false + source: transitionSource + + asynchronous: true + autoTransform: true + } + + Image { + id: frontImage + + anchors.fill: parent + fillMode: plasmoid.configuration.fillMode + + cache: false + source: activeSource + + asynchronous: true + autoTransform: true + + onWidthChanged: imageReloadTimer.restart() + onHeightChanged: imageReloadTimer.restart() + + sourceSize.width: width + sourceSize.height: height + + HoverHandler { + enabled: Plasmoid.configuration.leftClickOpenImage + cursorShape: Qt.PointingHandCursor + } + + TapHandler { + acceptedButtons: Qt.LeftButton + enabled: Plasmoid.configuration.leftClickOpenImage + onTapped: Qt.openUrlExternally(activeSource) + } + } + } + + // BUG TODO fix the rendering of the drop shadow + /* + DropShadow { + id: itemViewDropShadow + anchors.fill: parent + visible: imageView.visible && !plasmoid.configuration.useBackground + + radius: 8.0 + samples: 16 + color: "#80000000" + source: frontImage + } + */ + + } + + function setActiveSource(source) { + if(itemCount > 1) { // Only do transition if we have more that one item + transitionSource = source + faderAnimation.restart() + } else { + transitionSource = source + activeSource = source + } + } + + SequentialAnimation { + id: faderAnimation + + ParallelAnimation { + OpacityAnimator { target: frontImage; from: 1; to: 0; duration: Kirigami.Units.veryLongDuration } + OpacityAnimator { target: bufferImage; from: 0; to: 1; duration: Kirigami.Units.veryLongDuration } + } + ScriptAction { + script: { + // Copy the transitionSource + var ts = transitionSource + activeSource = ts + frontImage.opacity = 1 + transitionSource = "" + bufferImage.opacity = 0 + } + } + } + + DragDrop.DropArea { + id: dropArea + anchors.fill: parent + + onDrop: { + var mimeData = event.mimeData + if (mimeData.hasUrls) { + var urls = mimeData.urls + for (var i = 0, j = urls.length; i < j; ++i) { + var url = urls[i] + var type = items.isDir(url) ? "folder" : "file" + var item = { "path":url, "type":type } + addItem(item) + } + } + event.accept(Qt.CopyAction) + } + } + + Item { + id: overlay + + anchors.fill: parent + + visible: hasItems + opacity: overlayMouseArea.containsMouse ? 1 : 0 + + Behavior on opacity { + NumberAnimation {} + } + + PlasmaComponents3.Button { + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + enabled: (items.historyLength > 0) && !isTransitioning + visible: main.itemCount > 1 + icon.name: "arrow-left" + onClicked: { + nextTimer.stop() + previousItem() + } + } + + PlasmaComponents3.Button { + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + enabled: hasItems && !isTransitioning + visible: main.itemCount > 1 + icon.name: "arrow-right" + onClicked: { + nextTimer.stop() + nextItem() + } + } + + Row { + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottomMargin: Kirigami.Units.smallSpacing + + /* + PlasmaComponents3.Button { + icon.name: "documentinfo" + onClicked: { } + } + */ + PlasmaComponents3.Button { + + //text: activeSource.split("/").pop().slice(-25) + icon.name: "document-preview" + onClicked: Qt.openUrlExternally(main.activeSource) + + // PlasmaComponents3.ToolTip { + // text: activeSource + // } + } + /* + PlasmaComponents3.Button { + icon.name: "trash-empty" + onClicked: { } + } + + PlasmaComponents3.Button { + icon.name: "flag-black" + onClicked: { } + } + */ + } + + // BUG TODO Fix overlay so _all_ mouse events reach lower components + MouseArea { + id: overlayMouseArea + + anchors.fill: parent + hoverEnabled: true + + propagateComposedEvents: true + + //onClicked: mouse => mouse.accepted = false; + onPressed: mouse => mouse.accepted = false; + //onReleased: mouse => mouse.accepted = false; + onDoubleClicked: mouse => mouse.accepted = false; + //onPositionChanged: mouse => mouse.accepted = false; + //onPressAndHold: mouse => mouse.accepted = false; + + } + + } + + // Visualization of the count down + // TODO Makes plasmashell suck CPU until the universe or the computer collapse in on itself + /* + Rectangle { + id: progress + + visible: plasmoid.configuration.showCountdown && hasItems && itemCount > 1 + + color: "transparent" + + implicitWidth: Kirigami.Units.gridUnit + implicitHeight: implicitWidth + + Rectangle { + anchors.fill: parent + + opacity: pause ? 0.1 : 0.5 + + radius: width / 2 + color: "gray" + + Rectangle { + id: innerRing + anchors.fill: parent + + scale: 0 + + radius: width / 2 + + color: "lightblue" + + ScaleAnimator on scale { + running: nextTimer.running + loops: Animation.Infinite + from: 0; + to: 1; + duration: nextTimer.interval + } + + } + } + + Kirigami.Icon { + id: pauseIcon + visible: pause + anchors.fill: parent + source: "media-playback-pause" + } + } + */ + + PlasmaComponents3.Button { + + anchors.centerIn: parent + + visible: !hasItems + icon.name: "configure" + text: i18nc("@action:button", "Configure…") + onClicked: { + Plasmoid.internalAction("configure").trigger(); + } + } +} diff --git a/applets/mediaframe/package/metadata.json b/applets/mediaframe/package/metadata.json new file mode 100644 index 00000000..bb50684c --- /dev/null +++ b/applets/mediaframe/package/metadata.json @@ -0,0 +1,138 @@ +{ + "KPackageStructure": "Plasma/Applet", + "KPlugin": { + "Authors": [ + { + "Email": "dev.larpon@gmail.com", + "Name": "Lars Pontoppidan", + "Name[ar]": "لارس بونتوبيدان", + "Name[az]": "Lars Pontoppidan", + "Name[bg]": "Lars Pontoppidan", + "Name[ca@valencia]": "Lars Pontoppidan", + "Name[ca]": "Lars Pontoppidan", + "Name[cs]": "Lars Pontoppidan", + "Name[da]": "Lars Pontoppidan", + "Name[de]": "Lars Pontoppidan", + "Name[en_GB]": "Lars Pontoppidan", + "Name[eo]": "Lars Pontoppidan", + "Name[es]": "Lars Pontoppidan", + "Name[eu]": "Lars Pontoppidan", + "Name[fi]": "Lars Pontoppidan", + "Name[fr]": "Lars Pontoppidan", + "Name[gl]": "Lars Pontoppidan", + "Name[he]": "לארס פונטופידן", + "Name[hu]": "Lars Pontoppidan", + "Name[ia]": "Lars Pontoppidan", + "Name[id]": "Lars Pontoppidan", + "Name[is]": "Lars Pontoppidan", + "Name[it]": "Lars Pontoppidan", + "Name[ja]": "Lars Pontoppidan", + "Name[ka]": "Lars Pontoppidan", + "Name[ko]": "Lars Pontoppidan", + "Name[lt]": "Lars Pontoppidan", + "Name[lv]": "Lars Pontoppidan", + "Name[nl]": "Lars Pontoppidan", + "Name[nn]": "Lars Pontoppidan", + "Name[pl]": "Lars Pontoppidan", + "Name[pt]": "Lars Pontoppidan", + "Name[pt_BR]": "Lars Pontoppidan", + "Name[ro]": "Lars Pontoppidan", + "Name[ru]": "Lars Pontoppidan", + "Name[sk]": "Lars Pontoppidan", + "Name[sl]": "Lars Pontoppidan", + "Name[sv]": "Lars Pontoppidan", + "Name[tr]": "Lars Pontoppidan", + "Name[uk]": "Lars Pontoppidan", + "Name[vi]": "Lars Pontoppidan", + "Name[x-test]": "xxLars Pontoppidanxx", + "Name[zh_CN]": "Lars Pontoppidan", + "Name[zh_TW]": "Lars Pontoppidan" + } + ], + "BugReportUrl": "https://bugs.kde.org/enter_bug.cgi?product=kdeplasma-addons&component=Media Frame", + "Category": "Utilities", + "Description": "(Picture) frame", + "Description[ar]": "إطار (الصورة)", + "Description[bg]": "(Изображение) Рамка", + "Description[ca@valencia]": "Marc (fotografies)", + "Description[ca]": "Marc (fotografies)", + "Description[cs]": "Rámeček (obrázku)", + "Description[de]": "(Bilder-)Rahmen", + "Description[en_GB]": "(Picture) frame", + "Description[es]": "Marco (de imagen)", + "Description[eu]": "(Argazki) markoa", + "Description[fr]": "Trame (Image)", + "Description[gl]": "Marco (de foto)", + "Description[he]": "מסגרת (לתמונה)", + "Description[hu]": "Képkeret", + "Description[ia]": "(Picture) Frame - (Photogramma)", + "Description[it]": "(Immagine) cornice", + "Description[ka]": "(სურათი) კადრი", + "Description[nn]": "Bilet­ramme", + "Description[pl]": "(Zdjęcie) ramka", + "Description[ru]": "Слайд-шоу из любимых изображений", + "Description[sl]": "(Slika) okvir", + "Description[sv]": "(Bild)ram", + "Description[tr]": "(Resim) çerçeve(si)", + "Description[uk]": "(Картинна) рамка", + "Description[x-test]": "xx(Picture) framexx", + "Description[zh_CN]": "图片相框", + "Description[zh_TW]": "相框", + "Icon": "image-x-generic", + "Id": "org.kde.plasma.mediaframe", + "License": "GPL-2.0+", + "Name": "Media Frame", + "Name[ar]": "إطار صور", + "Name[az]": "Fotoçərçivə", + "Name[bg]": "Медийна рамка", + "Name[ca@valencia]": "Marc multimèdia", + "Name[ca]": "Marc multimèdia", + "Name[cs]": "Rámec médií", + "Name[da]": "Medieramme", + "Name[de]": "Medien-Rahmen", + "Name[en_GB]": "Media Frame", + "Name[eo]": "Media Frame", + "Name[es]": "Marco multimedia", + "Name[eu]": "Multimedia markoa", + "Name[fi]": "Mediakehys", + "Name[fr]": "Trame de média", + "Name[gl]": "Marco de contido", + "Name[he]": "מסגרת מדיה", + "Name[hu]": "Médiakeret", + "Name[ia]": "Quadro de Media", + "Name[id]": "Bingkai Media", + "Name[is]": "Myndefnisrammi", + "Name[it]": "Cornice media", + "Name[ja]": "メディアフレーム", + "Name[ka]": "მედიის ჩარჩო", + "Name[ko]": "미디어 프레임", + "Name[lt]": "Medijos rėmelis", + "Name[lv]": "Multimediju rāmis", + "Name[nl]": "Mediaframe", + "Name[nn]": "Medie­ramme", + "Name[pl]": "Ramka multimedialna", + "Name[pt]": "Moldura de Conteúdos", + "Name[pt_BR]": "Quadro multimídia", + "Name[ro]": "Ramă multimedia", + "Name[ru]": "Фоторамка", + "Name[sk]": "Multimediálny rámik", + "Name[sl]": "Okvir medijev", + "Name[sv]": "Mediaram", + "Name[tr]": "Ortam Çerçevesi", + "Name[uk]": "Медіарамка", + "Name[vi]": "Khung phương tiện", + "Name[x-test]": "xxMedia Framexx", + "Name[zh_CN]": "媒体相框", + "Name[zh_TW]": "媒體框架", + "Website": "https://kde.org/plasma-desktop/" + }, + "X-Plasma-API-Minimum-Version": "6.0", + "X-Plasma-DropMimeTypes": [ + "inode/directory", + "image/jpeg", + "image/png", + "image/svg+xml", + "image/svg+xml-compressed", + "image/bmp" + ] +} diff --git a/applets/mediaframe/plugin/mediaframe.cpp b/applets/mediaframe/plugin/mediaframe.cpp new file mode 100644 index 00000000..8729d7b8 --- /dev/null +++ b/applets/mediaframe/plugin/mediaframe.cpp @@ -0,0 +1,389 @@ +/* + * SPDX-FileCopyrightText: 2015 Lars Pontoppidan + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "mediaframe.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +MediaFrame::MediaFrame(QObject *parent) + : QObject(parent) +{ + const auto imageMimeTypeNames = QImageReader::supportedMimeTypes(); + QMimeDatabase mimeDb; + for (const auto &imageMimeTypeName : imageMimeTypeNames) { + const auto mimeType = mimeDb.mimeTypeForName(QLatin1String(imageMimeTypeName)); + m_filters << mimeType.globPatterns(); + } + qDebug() << "Added" << m_filters.count() << "filters"; + // qDebug() << m_filters; + m_next = 0; + + connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &MediaFrame::slotItemChanged); + connect(&m_watcher, &QFileSystemWatcher::fileChanged, this, &MediaFrame::slotItemChanged); +} + +MediaFrame::~MediaFrame() = default; + +int MediaFrame::count() const +{ + return m_allFiles.count(); +} + +bool MediaFrame::random() const +{ + return m_random; +} + +void MediaFrame::setRandom(bool random) +{ + if (random != m_random) { + m_random = random; + Q_EMIT randomChanged(); + } +} + +int MediaFrame::random(int min, int max) +{ + if (min > max) { + int temp = min; + min = max; + max = temp; + } + + // qDebug() << "random" << min << "<->" << max << "=" << ((qrand()%(max-min+1))+min); + return (QRandomGenerator::global()->bounded((max - min + 1)) + min); +} + +QString MediaFrame::getCacheDirectory() +{ + return QDir::temp().absolutePath(); +} + +QString MediaFrame::hash(const QString &str) +{ + return QString::fromLatin1(QCryptographicHash::hash(str.toUtf8(), QCryptographicHash::Md5).toHex()); +} + +bool MediaFrame::isDir(const QString &path) +{ + return QDir(path).exists(); +} + +bool MediaFrame::isDirEmpty(const QString &path) +{ + return (isDir(path) && QDir(path).entryInfoList(QDir::NoDotAndDotDot | QDir::AllEntries).isEmpty()); +} + +bool MediaFrame::isFile(const QString &path) +{ + // Check if the file exists and is not a directory + return (QFileInfo::exists(path) && QFileInfo(path).isFile()); +} + +void MediaFrame::add(const QString &path) +{ + add(path, AddOption::NON_RECURSIVE); +} + +void MediaFrame::add(const QString &path, AddOption option) +{ + if (isAdded(path)) { + qWarning() << "Path" << path << "already added"; + return; + } + + QUrl url = QUrl(path); + QString localPath = url.toString(QUrl::PreferLocalFile); + // qDebug() << "Local path" << localPath << "Path" << path; + + QStringList paths; + QString filePath; + + if (isDir(localPath)) { + if (!isDirEmpty(localPath)) { + QDirIterator dirIterator( + localPath, + m_filters, + QDir::Files, + (option == AddOption::RECURSIVE ? QDirIterator::Subdirectories | QDirIterator::FollowSymlinks : QDirIterator::NoIteratorFlags)); + + while (dirIterator.hasNext()) { + dirIterator.next(); + + filePath = "file://" + dirIterator.filePath(); + paths.append(filePath); + m_allFiles.append(filePath); + // qDebug() << "Appended" << filePath; + Q_EMIT countChanged(); + } + if (paths.count() > 0) { + m_pathMap.insert(path, paths); + qDebug() << "Added" << paths.count() << "files from" << path; + } else { + qWarning() << "No images found in directory" << path; + } + } else { + qWarning() << "Not adding empty directory" << path; + } + + // the pictures have to be sorted before adding them to the list, + // because the QDirIterator sorts them in a different way than QDir::entryList + // paths.sort(); + + } else if (isFile(localPath)) { + paths.append(path); + m_pathMap.insert(path, paths); + m_allFiles.append(path); + qDebug() << "Added" << paths.count() << "files from" << path; + Q_EMIT countChanged(); + } else { + if (url.isValid() && !url.isLocalFile()) { + qDebug() << "Adding" << url.toString() << "as remote file"; + paths.append(path); + m_pathMap.insert(path, paths); + m_allFiles.append(path); + Q_EMIT countChanged(); + } else { + qWarning() << "Path" << path << "is not a valid file url or directory"; + } + } +} + +void MediaFrame::clear() +{ + m_pathMap.clear(); + m_allFiles.clear(); + Q_EMIT countChanged(); +} + +void MediaFrame::watch(const QString &path) +{ + QUrl url = QUrl(path); + QString localPath = url.toString(QUrl::PreferLocalFile); + if (isFile(localPath)) { + if (!m_watchFile.isEmpty()) { + // qDebug() << "Removing" << m_watchFile << "from watch list"; + m_watcher.removePath(m_watchFile); + } else { + qDebug() << "Nothing in watch list"; + } + + // qDebug() << "watching" << localPath << "for changes"; + m_watcher.addPath(localPath); + m_watchFile = localPath; + } else { + qWarning() << "Can't watch remote file" << path << "for changes"; + } +} + +bool MediaFrame::isAdded(const QString &path) +{ + return (m_pathMap.contains(path)); +} + +void MediaFrame::get(QJSValue successCallback) +{ + get(successCallback, QJSValue::UndefinedValue); +} + +void MediaFrame::get(QJSValue successCallback, QJSValue errorCallback) +{ + int size = m_allFiles.count() - 1; + + QString path; + QString errorMessage; + QJSValueList args; + + if (size < 1) { + if (size == 0) { + path = m_allFiles.at(0); + + if (successCallback.isCallable()) { + args << QJSValue(path); + successCallback.call(args); + } + return; + } else { + errorMessage = QStringLiteral("No files available"); + qWarning() << errorMessage; + + args << QJSValue(errorMessage); + errorCallback.call(args); + return; + } + } + + if (m_random) { + path = m_allFiles.at(this->random(0, size)); + } else { + path = m_allFiles.at(m_next); + m_next++; + if (m_next > size) { + qDebug() << "Resetting next count from" << m_next << "due to queue size" << size; + m_next = 0; + } + } + + QUrl url = QUrl(path); + + if (url.isValid()) { + QString localPath = url.toString(QUrl::PreferLocalFile); + + if (!isFile(localPath)) { + m_filename = path.section(QLatin1Char('/'), -1); + + QString cachedFile = getCacheDirectory() + QLatin1Char('/') + hash(path) + QLatin1Char('_') + m_filename; + + if (isFile(cachedFile)) { + // File has been cached + qDebug() << path << "is cached as" << cachedFile; + + if (successCallback.isCallable()) { + args << QJSValue(cachedFile); + successCallback.call(args); + } + return; + } + + m_successCallback = successCallback; + m_errorCallback = errorCallback; + m_filename = cachedFile; + + qDebug() << path << "doesn't exist locally, trying remote."; + + KIO::StoredTransferJob *job = KIO::storedGet(url, KIO::NoReload, KIO::HideProgressInfo); + connect(job, &KJob::finished, this, &MediaFrame::slotFinished); + + } else { + if (successCallback.isCallable()) { + args << QJSValue(path); + successCallback.call(args); + } + return; + } + } else { + errorMessage = path + QLatin1String(" is not a valid URL"); + qCritical() << errorMessage; + + if (errorCallback.isCallable()) { + args << QJSValue(errorMessage); + errorCallback.call(args); + } + return; + } +} + +void MediaFrame::pushHistory(const QString &string) +{ + const int oldCount = m_history.count(); + + m_history.prepend(string); + + // Keep a sane history size + if (m_history.length() > 50) { + m_history.removeLast(); + } + + if (oldCount != m_history.count()) { + Q_EMIT historyLengthChanged(); + } +} + +QString MediaFrame::popHistory() +{ + if (m_history.isEmpty()) { + return QString(); + } + + const QString item = m_history.takeFirst(); + Q_EMIT historyLengthChanged(); + return item; +} + +int MediaFrame::historyLength() const +{ + return m_history.length(); +} + +void MediaFrame::pushFuture(const QString &string) +{ + m_future.prepend(string); + Q_EMIT futureLengthChanged(); +} + +QString MediaFrame::popFuture() +{ + if (m_future.isEmpty()) { + return QString(); + } + + const QString item = m_future.takeFirst(); + Q_EMIT futureLengthChanged(); + return item; +} + +int MediaFrame::futureLength() const +{ + return m_future.length(); +} + +void MediaFrame::slotItemChanged(const QString &path) +{ + Q_EMIT itemChanged(path); +} + +void MediaFrame::slotFinished(KJob *job) +{ + QString errorMessage; + QJSValueList args; + + if (job->error()) { + errorMessage = QLatin1String("Error loading image: ") + job->errorString(); + qCritical() << errorMessage; + + if (m_errorCallback.isCallable()) { + args << QJSValue(errorMessage); + m_errorCallback.call(args); + } + } else if (KIO::StoredTransferJob *transferJob = qobject_cast(job)) { + QImage image; + + // TODO make proper caching calls + QString path = m_filename; + qDebug() << "Saving download to" << path; + + image.loadFromData(transferJob->data()); + image.save(path); + + qDebug() << "Saved to" << path; + + if (m_successCallback.isCallable()) { + args << QJSValue(path); + m_successCallback.call(args); + } + } else { + errorMessage = QStringLiteral("Unknown error occurred"); + + qCritical() << errorMessage; + + if (m_errorCallback.isCallable()) { + args << QJSValue(errorMessage); + m_errorCallback.call(args); + } + } +} diff --git a/applets/mediaframe/plugin/mediaframe.h b/applets/mediaframe/plugin/mediaframe.h new file mode 100644 index 00000000..cadb1d57 --- /dev/null +++ b/applets/mediaframe/plugin/mediaframe.h @@ -0,0 +1,100 @@ +/* + * SPDX-FileCopyrightText: 2015 Lars Pontoppidan + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef MEDIAFRAME_H +#define MEDIAFRAME_H + +#include +#include +#include +#include +#include +#include + +#include + +class MediaFrame : public QObject +{ + Q_OBJECT + + Q_PROPERTY(int count READ count NOTIFY countChanged) + Q_PROPERTY(int historyLength READ historyLength NOTIFY historyLengthChanged) + Q_PROPERTY(int futureLength READ futureLength NOTIFY futureLengthChanged) + Q_PROPERTY(bool random READ random WRITE setRandom NOTIFY randomChanged) + +public: + enum AddOption { + NON_RECURSIVE, + RECURSIVE, + }; + Q_ENUM(AddOption) + + explicit MediaFrame(QObject *parent = nullptr); + ~MediaFrame() override; + + int count() const; + + int historyLength() const; + int futureLength() const; + + bool random() const; + void setRandom(bool random); + + Q_INVOKABLE bool isDir(const QString &path); + Q_INVOKABLE bool isDirEmpty(const QString &path); + Q_INVOKABLE bool isFile(const QString &path); + + Q_INVOKABLE void add(const QString &path); + Q_INVOKABLE void add(const QString &path, AddOption option); + Q_INVOKABLE void clear(); + + Q_INVOKABLE void watch(const QString &path); + + Q_INVOKABLE bool isAdded(const QString &path); + + Q_INVOKABLE void get(QJSValue callback); + Q_INVOKABLE void get(QJSValue callback, QJSValue error_callback); + + Q_INVOKABLE void pushHistory(const QString &string); + Q_INVOKABLE QString popHistory(); + + Q_INVOKABLE void pushFuture(const QString &string); + Q_INVOKABLE QString popFuture(); + +Q_SIGNALS: + void countChanged(); + void historyLengthChanged(); + void futureLengthChanged(); + void randomChanged(); + void itemChanged(const QString &path); + +private Q_SLOTS: + void slotItemChanged(const QString &path); + void slotFinished(KJob *job); + +private: + int random(int min, int max); + QString getCacheDirectory(); + QString hash(const QString &str); + + QStringList m_filters; + QHash m_pathMap; + QStringList m_allFiles; + QString m_watchFile; + QFileSystemWatcher m_watcher; + + QStringList m_history; + QStringList m_future; + + QJSValue m_successCallback; + QJSValue m_errorCallback; + QString m_filename; + + bool m_random = false; + int m_next = 0; +}; + +#endif diff --git a/applets/mediaframe/plugin/mediaframeplugin.cpp b/applets/mediaframe/plugin/mediaframeplugin.cpp new file mode 100644 index 00000000..37c83f77 --- /dev/null +++ b/applets/mediaframe/plugin/mediaframeplugin.cpp @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2015 Lars Pontoppidan + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "mediaframe.h" + +#include +#include + +class MediaFramePlugin : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") + +public: + void registerTypes(const char *uri) override + { + qmlRegisterType(uri, 2, 0, "MediaFrame"); + } +}; + +#include "mediaframeplugin.moc" diff --git a/applets/notes/CMakeLists.txt b/applets/notes/CMakeLists.txt new file mode 100644 index 00000000..cd93d0c3 --- /dev/null +++ b/applets/notes/CMakeLists.txt @@ -0,0 +1,18 @@ +add_definitions(-DTRANSLATION_DOMAIN="plasma_applet_org.kde.plasma.notes") + +plasma_install_package(package org.kde.plasma.notes) + +ecm_add_qml_module(notesplugin URI org.kde.plasma.private.notes) +target_sources(notesplugin PRIVATE + plugin/abstractnoteloader.cpp + plugin/documenthandler.cpp + plugin/filesystemnoteloader.cpp + plugin/note.cpp + plugin/notemanager.cpp + plugin/notesplugin.cpp +) +target_link_libraries(notesplugin PRIVATE + Qt::Quick + KF6::CoreAddons +) +ecm_finalize_qml_module(notesplugin) diff --git a/applets/notes/Messages.sh b/applets/notes/Messages.sh new file mode 100755 index 00000000..257d5873 --- /dev/null +++ b/applets/notes/Messages.sh @@ -0,0 +1,2 @@ +#! /usr/bin/env bash +$XGETTEXT `find . -name \*.qml -o -name \*.cpp` -o $podir/plasma_applet_org.kde.plasma.notes.pot diff --git a/applets/notes/package/contents/config/config.qml b/applets/notes/package/contents/config/config.qml new file mode 100644 index 00000000..85905c54 --- /dev/null +++ b/applets/notes/package/contents/config/config.qml @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2015 Kai Uwe Broulik + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + */ + +import QtQuick 2.15 + +import org.kde.plasma.configuration 2.0 + +ConfigModel { + ConfigCategory { + name: i18nc("@title", "Appearance") + icon: "knotes" + source: "configAppearance.qml" + } +} diff --git a/applets/notes/package/contents/config/main.xml b/applets/notes/package/contents/config/main.xml new file mode 100644 index 00000000..521cd147 --- /dev/null +++ b/applets/notes/package/contents/config/main.xml @@ -0,0 +1,34 @@ + + + + + + + + yellow + + + + + + + + + + + 0 + + + + 0 + + + + 0 + + + + diff --git a/applets/notes/package/contents/ui/ShortcutMenuItem.qml b/applets/notes/package/contents/ui/ShortcutMenuItem.qml new file mode 100644 index 00000000..4f92ce14 --- /dev/null +++ b/applets/notes/package/contents/ui/ShortcutMenuItem.qml @@ -0,0 +1,27 @@ +/* + * SPDX-FileCopyrightText: 2019 Luca Carlon + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Window 2.15 + +MenuItem { + property var _sequence: [] + property alias _text: actionElement.text + property alias _enabled: actionElement.enabled + property alias _iconName: actionElement.icon.name + + Shortcut { + id: shortcutElement + sequences: [_sequence] + enabled: false + } + + action: Action { + id: actionElement + shortcut: shortcutElement.sequences ? shortcutElement.sequences[0] : null + } +} diff --git a/applets/notes/package/contents/ui/configAppearance.qml b/applets/notes/package/contents/ui/configAppearance.qml new file mode 100644 index 00000000..909f31af --- /dev/null +++ b/applets/notes/package/contents/ui/configAppearance.qml @@ -0,0 +1,93 @@ +/* + * SPDX-FileCopyrightText: 2015 Kai Uwe Broulik + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + */ + +import QtQuick 2.15 +import QtQuick.Controls 2.15 as QQC2 +import QtQuick.Layouts 1.15 + +import org.kde.kcmutils as KCM +import org.kde.plasma.core as PlasmaCore +import org.kde.kirigami 2.20 as Kirigami +import org.kde.ksvg 1.0 as KSvg +import org.kde.plasma.components 3.0 as PlasmaComponents3 +import org.kde.kirigami 2.5 as Kirigami +import org.kde.kcmutils as KCM + +KCM.GridViewKCM { + property string cfg_color + property alias cfg_fontSize: fontSizeSpinBox.value + + header: Kirigami.FormLayout { + QQC2.SpinBox { + id: fontSizeSpinBox + + implicitWidth: Kirigami.Units.gridUnit * 3 + from: 4 + to: 128 + textFromValue: function (value) { + return i18n("%1pt", value) + } + valueFromText: function (text) { + return parseInt(text) + } + + Kirigami.FormData.label: i18n("Text font size:") + } + } + + view.model: ["white", "black", "red", "orange", "yellow", "green", "blue", "pink", "translucent", "translucent-light"] + view.currentIndex: view.model.indexOf(cfg_color) + view.onCurrentIndexChanged: cfg_color = view.model[view.currentIndex] + + view.delegate: KCM.GridDelegate { + id: delegate + thumbnailAvailable: true + thumbnail: KSvg.SvgItem { + anchors.fill: parent + anchors.margins: Kirigami.Units.largeSpacing + + imagePath: "widgets/notes" + elementId: modelData + "-notes" + + QQC2.Label { + anchors.fill: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + + text: { + switch (modelData) { + case "white": return i18n("A white sticky note") + case "black": return i18n("A black sticky note") + case "red": return i18n("A red sticky note") + case "orange": return i18n("An orange sticky note") + case "yellow": return i18n("A yellow sticky note") + case "green": return i18n("A green sticky note") + case "blue": return i18n("A blue sticky note") + case "pink": return i18n("A pink sticky note") + case "translucent": return i18n("A transparent sticky note") + case "translucent-light": return i18n("A transparent sticky note with light text") + } + } + textFormat: Text.PlainText + elide: Text.ElideRight + wrapMode: Text.WordWrap + + //this is deliberately _NOT_ the theme color as we are over a known bright background + //an unknown colour over a known colour is a bad move as you end up with white on yellow + color: { + if (modelData === "black" || modelData === "translucent-light") { + return "#dfdfdf" + } else { + return "#202020" + } + } + } + } + onClicked: { + cfg_color = modelData + } + } +} diff --git a/applets/notes/package/contents/ui/main.qml b/applets/notes/package/contents/ui/main.qml new file mode 100644 index 00000000..470f0903 --- /dev/null +++ b/applets/notes/package/contents/ui/main.qml @@ -0,0 +1,656 @@ +/* + SPDX-FileCopyrightText: 2014 David Edmundson + SPDX-FileCopyrightText: 2014, 2015 Kai Uwe Broulik + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +import QtQuick +import QtQuick.Controls as QQC2 // just for desktop-styled context menus +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Dialogs + +import org.kde.draganddrop 2.0 as DragDrop + +import org.kde.plasma.core as PlasmaCore +import org.kde.kirigami 2.20 as Kirigami +import org.kde.ksvg 1.0 as KSvg +import org.kde.plasma.components 3.0 as PlasmaComponents3 +import org.kde.plasma.extras 2.0 as PlasmaExtras +import org.kde.plasma.plasmoid 2.0 + +import org.kde.plasma.private.notes 0.1 + +PlasmoidItem { + id: root + + switchWidth: Kirigami.Units.gridUnit * 5 + switchHeight: Kirigami.Units.gridUnit * 5 + + Plasmoid.backgroundHints: PlasmaCore.Types.NoBackground + + // this isn't a frameSVG, the default SVG margins take up around 7% of the frame size, so we use that + readonly property real horizontalMargins: fullRepresentationItem.width * 0.07 + readonly property real verticalMargins: fullRepresentationItem.height * 0.07 + readonly property PlasmaComponents3.TextArea mainTextArea: fullRepresentationItem.mainTextArea + + // note is of type Note + property QtObject note: noteManager.loadNote(Plasmoid.configuration.noteId); + + // define colors used for icons in ToolButtons and for text in TextArea. + // this is deliberately _NOT_ the theme color as we are over a known bright background! + // an unknown colour over a known colour is a bad move as you end up with white on yellow. + readonly property color textIconColor: { + if (Plasmoid.configuration.color === "black" || Plasmoid.configuration.color === "translucent-light") { + return "#dfdfdf"; + } + return "#202020"; + } + + onExternalData: (mimetype, data) => { + // if we dropped a text file, we want its contents, + // otherwise we take the external data verbatim + var contents = NotesHelper.fileContents(data) || data + mainTextArea.text = String(contents).replace(/\n/g, "
") // what about richtext? + + // place cursor at the end of text, there's no "just move the cursor" function + mainTextArea.moveCursorSelection(mainTextArea.length) + mainTextArea.deselect() + } + + Timer { + id: forceFocusTimer + interval: 1 + onTriggered: mainTextArea.forceActiveFocus() + } + + Connections { + target: Plasmoid + function onActivated() { + // FIXME doing forceActiveFocus here directly doesn't work + forceFocusTimer.restart() + } + } + + NoteManager { + id: noteManager + } + + // Only exists because the default CompactRepresentation doesn't open on drag. + // TODO remove once it gains that feature (perhaps optionally?) + compactRepresentation: DragDrop.DropArea { + id: compactDropArea + onDragEnter: activationTimer.restart() + onDragLeave: activationTimer.stop() + + Timer { + id: activationTimer + interval: 250 // matches taskmanager delay + onTriggered: root.expanded = true + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + + property bool wasExpanded + + onPressed: wasExpanded = root.expanded + onClicked: root.expanded = !wasExpanded + + Kirigami.Icon { + anchors.fill: parent + source: "knotes-symbolic" + active: parent.containsMouse + } + } + } + + preloadFullRepresentation: true + fullRepresentation: KSvg.SvgItem { + id: backgroundItem + + property alias mainTextArea: mainTextArea + Layout.preferredWidth: Kirigami.Units.gridUnit * 25 + Layout.preferredHeight: Kirigami.Units.gridUnit * 25 + Layout.minimumWidth: Kirigami.Units.gridUnit * 2 + Layout.minimumHeight: Kirigami.Units.gridUnit * 2 + + imagePath: "widgets/notes" + elementId: Plasmoid.configuration.color + "-notes" + + DocumentHandler { + id: documentHandler + target: mainTextArea + cursorPosition: mainTextArea.cursorPosition + selectionStart: mainTextArea.selectionStart + selectionEnd: mainTextArea.selectionEnd + defaultFontSize: Plasmoid.configuration.fontSize + } + + Connections { + target: root + function onExpandedChanged(expanded) { + // don't autofocus when we're on the desktop + if (expanded && (Plasmoid.formFactor === PlasmaCore.Types.Vertical || Plasmoid.formFactor === PlasmaCore.Types.Horizontal)) { + mainTextArea.forceActiveFocus() + } + } + } + Component.onDestruction: { + note.save(mainTextArea.text); + } + + FocusScope { + id: focusScope + anchors { + fill: parent + leftMargin: horizontalMargins + rightMargin: horizontalMargins + topMargin: verticalMargins + bottomMargin: verticalMargins + } + + PlasmaComponents3.ScrollView { + id: scrollview + anchors { + top: parent.top + left: parent.left + right: parent.right + bottom: fontButtons.top + bottomMargin: Kirigami.Units.largeSpacing + } + + clip: true + + PlasmaComponents3.TextArea { + id: mainTextArea + property int cfgFontPointSize: Plasmoid.configuration.fontSize + + textFormat: TextEdit.RichText + onLinkActivated: Qt.openUrlExternally(link) + background: null + color: textIconColor + persistentSelection: true + wrapMode: TextEdit.Wrap + + font.pointSize: cfgFontPointSize + + Keys.onPressed: event => { + if (event.key === Qt.Key_Escape) { + root.expanded = false; + event.accepted = true; + } else if (event.modifiers === Qt.ControlModifier) { + if (event.key === Qt.Key_B) { + documentHandler.bold = !documentHandler.bold; + event.accepted = true; + } else if (event.key === Qt.Key_I) { + documentHandler.italic = !documentHandler.italic; + event.accepted = true; + } else if (event.key === Qt.Key_U) { + documentHandler.underline = !documentHandler.underline; + event.accepted = true; + } else if (event.key === Qt.Key_S) { + documentHandler.strikeOut = !documentHandler.strikeOut; + event.accepted = true; + } else if (event.matches(StandardKey.Paste)) { + documentHandler.pasteWithoutFormatting(); + documentHandler.reset(); + event.accepted = true; + } else if (event.matches(StandardKey.Cut)) { + cut(); + documentHandler.reset(); + event.accepted = true; + } + } + } + + // Apply the font size change to existing texts + onCfgFontPointSizeChanged: { + const start = selectionStart; + const end = selectionEnd; + + selectAll(); + documentHandler.fontSize = cfgFontPointSize; + select(start, end); + } + + // update the note if the source changes, but only if the user isn't editing it currently + Binding { + target: mainTextArea + property: "text" + value: note.noteText + when: !mainTextArea.activeFocus + // don't restore an empty value (which IS empty by default when the applet starts up), + // instead only remove this binding for the time when the user edits the content. + restoreMode: Binding.RestoreBinding + } + + onActiveFocusChanged: { + const window = Window.window; + if (activeFocus && window && (window.flags & Qt.WindowDoesNotAcceptFocus)) { + Plasmoid.status = PlasmaCore.Types.AcceptingInputStatus + } else { + Plasmoid.status = PlasmaCore.Types.ActiveStatus + note.save(text); + } + } + + onPressed: event => { + if (event.button === Qt.RightButton) { + event.accepted = true; + contextMenu.popup(); + forceActiveFocus(); + } + if (event.button === Qt.LeftButton && contextMenu.visible === true) { + event.accepted = true; + contextMenu.dismiss(); + forceActiveFocus(); + } + } + + Component.onCompleted: { + if (!Plasmoid.configuration.fontSize) { + // Set fontSize to default if it is not set + Plasmoid.configuration.fontSize = font.pointSize + } + } + + QQC2.Menu { + id: contextMenu + + ShortcutMenuItem { + _sequence: StandardKey.Undo + _enabled: mainTextArea.canUndo + _iconName: "edit-undo" + _text: i18n("Undo") + onTriggered: contextMenu.retFocus(() => mainTextArea.undo()) + } + + ShortcutMenuItem { + _sequence: StandardKey.Redo + _enabled: mainTextArea.canRedo + _iconName: "edit-redo" + _text: i18n("Redo") + onTriggered: contextMenu.retFocus(() => mainTextArea.redo()) + } + + QQC2.MenuSeparator {} + + ShortcutMenuItem { + _sequence: StandardKey.Cut + _enabled: mainTextArea.selectedText.length > 0 + _iconName: "edit-cut" + _text: i18n("Cut") + onTriggered: contextMenu.retFocus(() => mainTextArea.cut()) + } + + ShortcutMenuItem { + _sequence: StandardKey.Copy + _enabled: mainTextArea.selectedText.length > 0 + _iconName: "edit-copy" + _text: i18n("Copy") + onTriggered: contextMenu.retFocus(() => mainTextArea.copy()) + } + + ShortcutMenuItem { + _sequence: StandardKey.Paste + _enabled: mainTextArea.canPaste + _iconName: "edit-paste" + _text: i18n("Paste") + onTriggered: contextMenu.retFocus(() => documentHandler.pasteWithoutFormatting()) + } + + ShortcutMenuItem { + _enabled: mainTextArea.canPaste + _text: i18n("Paste with Full Formatting") + _iconName: "edit-paste" + onTriggered: contextMenu.retFocus(() => mainTextArea.paste()) + } + + ShortcutMenuItem { + _enabled: mainTextArea.selectedText.length > 0 + _text: i18nc("@action:inmenu", "Remove Formatting") + _iconName: "edit-clear-all" + onTriggered: { + var richText = mainTextArea.getFormattedText(mainTextArea.selectionStart, mainTextArea.selectionEnd) + var unformattedText = documentHandler.strip(richText) + unformattedText = unformattedText.replace(/\n/g, "
") + mainTextArea.remove(mainTextArea.selectionStart, mainTextArea.selectionEnd) + contextMenu.retFocus(() => mainTextArea.insert(mainTextArea.selectionStart, unformattedText)) + } + } + + ShortcutMenuItem { + _sequence: StandardKey.Delete + _enabled: mainTextArea.selectedText.length > 0 + _iconName: "edit-delete" + _text: i18n("Delete") + onTriggered: contextMenu.retFocus(() => mainTextArea.remove(mainTextArea.selectionStart, mainTextArea.selectionEnd)) + } + + ShortcutMenuItem { + _enabled: mainTextArea.text.length > 0 + _iconName: "edit-clear" + _text: i18n("Clear") + onTriggered: contextMenu.retFocus(() => mainTextArea.clear()) + } + + QQC2.MenuSeparator {} + + ShortcutMenuItem { + _sequence: StandardKey.SelectAll + _enabled: mainTextArea.text.length > 0 + _iconName: "edit-select-all" + _text: i18n("Select All") + onTriggered: contextMenu.retFocus(() => mainTextArea.selectAll()) + } + + function retFocus(f) { + f() + documentHandler.reset() + mainTextArea.forceActiveFocus() + } + } + } + + // Save scrolling position when it changes, but throttle to avoid + // killing a storage disk. + Connections { + target: scrollview.contentItem + function onContentXChanged() { + throttedScrollSaver.restart(); + } + function onContentYChanged() { + throttedScrollSaver.restart(); + } + } + Connections { + target: mainTextArea + function onCursorPositionChanged() { + throttedScrollSaver.restart(); + } + } + + Timer { + id: throttedScrollSaver + interval: Kirigami.Units.humanMoment + repeat: false + running: false + onTriggered: scrollview.saveScroll() + } + + function saveScroll() { + const flickable = scrollview.contentItem; + Plasmoid.configuration.scrollX = flickable.contentX; + Plasmoid.configuration.scrollY = flickable.contentY; + Plasmoid.configuration.cursorPosition = mainTextArea.cursorPosition; + } + + function restoreScroll() { + const flickable = scrollview.contentItem; + flickable.contentX = Plasmoid.configuration.scrollX; + flickable.contentY = Plasmoid.configuration.scrollY; + mainTextArea.cursorPosition = Plasmoid.configuration.cursorPosition; + } + + // Give it some time to lay out the text, because at this + // point in time content size is not reliable yet. + Component.onCompleted: Qt.callLater(restoreScroll) + Component.onDestruction: saveScroll() + } + + DragDrop.DropArea { + id: dropArea + anchors.fill: scrollview + + function positionOfDrop(event) { + return mainTextArea.positionAt(event.x, event.y) + } + + onDrop: { + var mimeData = event.mimeData + var text = "" + if (mimeData.hasUrls) { + var urls = mimeData.urls + for (var i = 0, j = urls.length; i < j; ++i) { + var url = urls[i] + text += "" + url + "
" + } + } else { + text = mimeData.text.replace(/\n/g, "
") + } + + mainTextArea.insert(positionOfDrop(event), text) + event.accept(Qt.CopyAction) + } + onDragMove: { + // there doesn't seem to be a "just move the cursor", so we move + // the selection and then unselect so the cursor follows the mouse + mainTextArea.moveCursorSelection(positionOfDrop(event)) + mainTextArea.deselect() + } + + MouseArea { + anchors.fill: parent + cursorShape: mainTextArea.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor + acceptedButtons: Qt.NoButton + } + } + + RowLayout { + id: fontButtons + spacing: Kirigami.Units.smallSpacing + anchors { + bottom: parent.bottom + left: parent.left + right: parent.right + } + height: visible ? implicitHeight : 0 + visible: opacity > 0 + opacity: focusScope.activeFocus ? 1 : 0 + Behavior on opacity { NumberAnimation { duration: Kirigami.Units.longDuration } } + + readonly property int requiredWidth: formatButtonsRow.width + spacing + settingsButton.width + removeButton.width + readonly property bool showFormatButtons: width > requiredWidth + + Row { + id: formatButtonsRow + spacing: Kirigami.Units.smallSpacing + // show format buttons if TextField or any of the buttons have focus + enabled: opacity > 0 + visible: fontButtons.showFormatButtons + + PlasmaComponents3.ToolButton { + focusPolicy: Qt.TabFocus + icon.name: "format-text-bold" + icon.color: textIconColor + checked: documentHandler.bold + onClicked: documentHandler.bold = !documentHandler.bold + Accessible.name: boldTooltip.text + PlasmaComponents3.ToolTip { + id: boldTooltip + text: i18nc("@info:tooltip", "Bold") + } + } + PlasmaComponents3.ToolButton { + focusPolicy: Qt.TabFocus + icon.name: "format-text-italic" + icon.color: textIconColor + checked: documentHandler.italic + onClicked: documentHandler.italic = !documentHandler.italic + Accessible.name: italicTooltip.text + PlasmaComponents3.ToolTip { + id: italicTooltip + text: i18nc("@info:tooltip", "Italic") + } + } + PlasmaComponents3.ToolButton { + focusPolicy: Qt.TabFocus + icon.name: "format-text-underline" + icon.color: textIconColor + checked: documentHandler.underline + onClicked: documentHandler.underline = !documentHandler.underline + Accessible.name: underlineTooltip.text + PlasmaComponents3.ToolTip { + id: underlineTooltip + text: i18nc("@info:tooltip", "Underline") + } + } + PlasmaComponents3.ToolButton { + focusPolicy: Qt.TabFocus + icon.name: "format-text-strikethrough" + icon.color: textIconColor + checked: documentHandler.strikeOut + onClicked: documentHandler.strikeOut = !documentHandler.strikeOut + Accessible.name: strikethroughTooltip.text + PlasmaComponents3.ToolTip { + id: strikethroughTooltip + text: i18nc("@info:tooltip", "Strikethrough") + } + } + } + + Item { // spacer + Layout.fillWidth: true + Layout.fillHeight: true + } + + PlasmaComponents3.ToolButton { + id: settingsButton + focusPolicy: Qt.TabFocus + icon.name: "configure" + icon.color: textIconColor + onClicked: Plasmoid.internalAction("configure").trigger() + Accessible.name: settingsTooltip.text + PlasmaComponents3.ToolTip { + id: settingsTooltip + text: Plasmoid.internalAction("configure").text + } + } + + PlasmaComponents3.ToolButton { + id: removeButton + focusPolicy: Qt.TabFocus + icon.name: "edit-delete" + icon.color: textIconColor + onClicked: { + // No need to ask for confirmation in the cases when... + // ...the note is blank + if (mainTextArea.length === 0 || + // ...the note's content is equal to the clipboard text + + // Note that we are intentionally not using + // mainTextArea.getText() because it has a method of + // converting the text to plainText that does not produce + // the same exact output of various other methods, and if + // we go out of our way to match it, we will be + // depending on an implementation detail. So we instead + // roll our own version to ensure that the conversion + // is done in the same way every time. + documentHandler.stripAndSimplify(mainTextArea.text) === documentHandler.strippedClipboardText() + ) { + Plasmoid.internalAction("remove").trigger(); + } else { + discardConfirmationDialogLoader.open(); + } + } + Accessible.name: removeTooltip.text + PlasmaComponents3.ToolTip { + id: removeTooltip + text: Plasmoid.internalAction("remove").text + } + } + } + } + + Loader { + id: discardConfirmationDialogLoader + + function open() { + if (item) { + item.open(); + } else { + active = true; + } + item.visible = true; + } + + active: false + + sourceComponent: MessageDialog { + visible: false + title: i18n("Discard this note?") + text: i18n("Are you sure you want to discard this note?") + + buttons: MessageDialog.Discard | MessageDialog.Cancel + + onButtonClicked: (button, role) => { + if (button === MessageDialog.Discard) { + Plasmoid.internalAction("remove").trigger() + visible = false; + } + } + + onRejected: { + visible = false + } + } + } + } + + Plasmoid.contextualActions: [ + PlasmaCore.Action { + text: i18nc("@item:inmenu", "White") + onTriggered: Plasmoid.configuration.color = "white" + }, + PlasmaCore.Action { + text: i18nc("@item:inmenu", "Black") + onTriggered: Plasmoid.configuration.color = "black" + }, + PlasmaCore.Action { + text: i18nc("@item:inmenu", "Red") + onTriggered: Plasmoid.configuration.color = "red" + }, + PlasmaCore.Action { + text: i18nc("@item:inmenu", "Orange") + onTriggered: Plasmoid.configuration.color = "orange" + }, + PlasmaCore.Action { + text: i18nc("@item:inmenu", "Yellow") + onTriggered: Plasmoid.configuration.color = "yellow" + }, + PlasmaCore.Action { + text: i18nc("@item:inmenu", "Green") + onTriggered: Plasmoid.configuration.color = "green" + }, + PlasmaCore.Action { + text: i18nc("@item:inmenu", "Blue") + onTriggered: Plasmoid.configuration.color = "blue" + }, + PlasmaCore.Action { + text: i18nc("@item:inmenu", "Pink") + onTriggered: Plasmoid.configuration.color = "pink" + }, + PlasmaCore.Action { + text: i18nc("@item:inmenu", "Transparent") + onTriggered: Plasmoid.configuration.color = "translucent" + }, + PlasmaCore.Action { + text: i18nc("@item:inmenu", "Transparent Light") + onTriggered: Plasmoid.configuration.color = "translucent-light" + }, + PlasmaCore.Action { + isSeparator: true + } + ] + + Component.onCompleted: { + // Plasmoid configuration doesn't check before emitting change signal + // explicit check is needed (at time of writing) + if (note.id != Plasmoid.configuration.noteId) { + Plasmoid.configuration.noteId = note.id; + } + } +} diff --git a/applets/notes/package/metadata.json b/applets/notes/package/metadata.json new file mode 100644 index 00000000..5d307565 --- /dev/null +++ b/applets/notes/package/metadata.json @@ -0,0 +1,150 @@ +{ + "KPackageStructure": "Plasma/Applet", + "KPlugin": { + "Authors": [ + { + "Email": "bettio@kde.org, lukas.krop@gmail.com", + "Name": "Davide Bettio, Lukas Kropatschek", + "Name[ar]": "دافيد بيتيو، لوكاس كروباتشيك", + "Name[az]": "Davide Bettio, Lukas Kropatschek", + "Name[bg]": "Davide Bettio, Lukas Kropatschek", + "Name[ca@valencia]": "Davide Bettio, Lukas Kropatschek", + "Name[ca]": "Davide Bettio, Lukas Kropatschek", + "Name[cs]": "Davide Bettio, Lukas Kropatschek", + "Name[da]": "Davide Bettio, Lukas Kropatschek", + "Name[de]": "Davide Bettio, Lukas Kropatschek", + "Name[en_GB]": "Davide Bettio, Lukas Kropatschek", + "Name[eo]": "Davide Bettio, Lukas Kropatschek", + "Name[es]": "Davide Bettio, Lukas Kropatschek", + "Name[eu]": "Davide Bettio, Lukas Kropatschek", + "Name[fi]": "Davide Bettio, Lukas Kropatschek", + "Name[fr]": "Davide Bettio, Lukas Kropatschek", + "Name[gl]": "Davide Bettio, Lukas Kropatschek", + "Name[he]": "דוד בטיו, לוקאס קרופאצ׳ק", + "Name[hu]": "Davide Bettio, Lukas Kropatschek", + "Name[ia]": "Davide Bettio, Lukas Kropatschek", + "Name[id]": "Davide Bettio, Lukas Kropatschek", + "Name[is]": "Davide Bettio, Lukas Kropatschek", + "Name[it]": "Davide Bettio, Lukas Kropatschek", + "Name[ja]": "Davide Bettio, Lukas Kropatschek", + "Name[ka]": "Davide Bettio, Lukas Kropatschek", + "Name[ko]": "Davide Bettio, Lukas Kropatschek", + "Name[lt]": "Davide Bettio, Lukas Kropatschek", + "Name[lv]": "Davide Bettio, Lukas Kropatschek", + "Name[nl]": "Davide Bettio, Lukas Kropatschek", + "Name[nn]": "Davide Bettio, Lukas Kropatschek", + "Name[pl]": "Davide Bettio, Lukas Kropatschek", + "Name[pt]": "Davide Bettio, Lukas Kropatschek", + "Name[pt_BR]": "Davide Bettio, Lukas Kropatschek", + "Name[ro]": "Davide Bettio, Lukas Kropatschek", + "Name[ru]": "Davide Bettio, Lukas Kropatschek", + "Name[sk]": "Davide Bettio, Lukas Kropatschek", + "Name[sl]": "Davide Bettio, Lukas Kropatschek", + "Name[sv]": "Davide Bettio, Lukas Kropatschek", + "Name[tr]": "Davide Bettio, Lukas Kropatschek", + "Name[uk]": "Davide Bettio, Lukas Kropatschek", + "Name[vi]": "Davide Bettio, Lukas Kropatschek", + "Name[x-test]": "xxDavide Bettio, Lukas Kropatschekxx", + "Name[zh_CN]": "Davide Bettio, Lukas Kropatschek", + "Name[zh_TW]": "Davide Bettio, Lukas Kropatschek" + } + ], + "BugReportUrl": "https://bugs.kde.org/enter_bug.cgi?product=kdeplasma-addons&component=notes", + "Category": "Utilities", + "Description": "Desktop sticky note", + "Description[ar]": "ملاحظة سطح مكتب لاصقة", + "Description[az]": "İş masası qeyd vərəqi", + "Description[bg]": "Лепкава бележка на работния плот", + "Description[ca@valencia]": "Nota adhesiva per a l'escriptori", + "Description[ca]": "Nota adhesiva per a l'escriptori", + "Description[cs]": "Lepicí poznámka na plochu", + "Description[da]": "Post-it note på skrivebordet", + "Description[de]": "Haftnotiz für die Arbeitsfläche", + "Description[en_GB]": "Desktop sticky note", + "Description[eo]": "Surtabla glueca noto", + "Description[es]": "Nota adhesiva del escritorio", + "Description[eu]": "Mahaigaineko ohar itsaskorra", + "Description[fi]": "Työpöydän tarralappu", + "Description[fr]": "Note auto-collante de bureau", + "Description[gl]": "Notas no escritorio", + "Description[he]": "פתק נצמד לשולחן העבודה", + "Description[hu]": "Asztali cetlik", + "Description[ia]": "Nota collose de scriptorio", + "Description[id]": "Catatan lekat desktop", + "Description[is]": "Minnismiðar á skjáborði", + "Description[it]": "Note adesive del desktop", + "Description[ja]": "デスクトップの付箋", + "Description[ka]": "სამუშაო მაგიდის წებოვანი შენიშვნა", + "Description[ko]": "바탕 화면 메모", + "Description[lt]": "Lipnus darbalaukio lapelis", + "Description[lv]": "Piezīmju lapiņa darbvirsmai", + "Description[nl]": "Plaknotitie op het bureaublad", + "Description[nn]": "Notatlapp på skrivebordet", + "Description[pl]": "Żółte karteczki na pulpicie", + "Description[pt]": "Nota autocolante do ecrã", + "Description[pt_BR]": "Nota adesiva na área de trabalho", + "Description[ro]": "Notiță de birou lipicioasă", + "Description[ru]": "Клейкая заметка на рабочем столе", + "Description[sk]": "Lepiaca poznámka na plochu", + "Description[sl]": "Namizni lepljivi listek", + "Description[sv]": "Klisterlapp med meddelande för skrivbordet", + "Description[tr]": "Masaüstü yapışkan notu", + "Description[uk]": "Стільничні липкі нотатки", + "Description[vi]": "GIấy nhớ trên bàn làm việc", + "Description[x-test]": "xxDesktop sticky notexx", + "Description[zh_CN]": "桌面记事贴", + "Description[zh_TW]": "桌面便條貼", + "Icon": "knotes", + "Id": "org.kde.plasma.notes", + "License": "GPL-2.0+", + "Name": "Sticky Note", + "Name[ar]": "ملاحظة لاصقة", + "Name[az]": "Qeyd vərəqi", + "Name[bg]": "Лепкава бележка", + "Name[ca@valencia]": "Nota adhesiva", + "Name[ca]": "Nota adhesiva", + "Name[cs]": "Lepicí poznámka", + "Name[da]": "Post-it note", + "Name[de]": "Haftnotiz", + "Name[en_GB]": "Sticky Note", + "Name[eo]": "Gluita Noto", + "Name[es]": "Notas adhesivas", + "Name[eu]": "Ohar itsaskorra", + "Name[fi]": "Tarralappu", + "Name[fr]": "Note auto-collante", + "Name[gl]": "Nota", + "Name[he]": "פתקים נצמדים", + "Name[hu]": "Cetlik", + "Name[ia]": "Nota collose", + "Name[id]": "Sticky Note", + "Name[is]": "Minnismiði", + "Name[it]": "Nota adesiva", + "Name[ja]": "付箋", + "Name[ka]": "წებოვანი შენიშნვა", + "Name[ko]": "메모지", + "Name[lt]": "Lipnus lapelis", + "Name[lv]": "Piezīmju lapiņa", + "Name[nl]": "Plaknotitie", + "Name[nn]": "Notat­lapp", + "Name[pl]": "Żółte karteczki", + "Name[pt]": "Nota Autocolante", + "Name[pt_BR]": "Nota adesiva", + "Name[ro]": "Notă lipicioasă", + "Name[ru]": "Клейкая заметка", + "Name[sk]": "Lepiaca poznámka", + "Name[sl]": "Lepljiv listek", + "Name[sv]": "Klisterlapp", + "Name[tr]": "Yapışkan Not", + "Name[uk]": "Липка нотатка", + "Name[vi]": "Giấy nhớ", + "Name[x-test]": "xxSticky Notexx", + "Name[zh_CN]": "记事贴", + "Name[zh_TW]": "便條貼", + "Website": "https://kde.org/plasma-desktop/" + }, + "X-Plasma-API-Minimum-Version": "6.0", + "X-Plasma-DropMimeTypes": [ + "text/plain", + "text/richtext" + ] +} diff --git a/applets/notes/plugin/abstractnoteloader.cpp b/applets/notes/plugin/abstractnoteloader.cpp new file mode 100644 index 00000000..51bae7a0 --- /dev/null +++ b/applets/notes/plugin/abstractnoteloader.cpp @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2014 David Edmundson + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + */ + +#include "abstractnoteloader.h" + +#include + +AbstractNoteLoader::AbstractNoteLoader() +{ +} + +AbstractNoteLoader::~AbstractNoteLoader() +{ +} diff --git a/applets/notes/plugin/abstractnoteloader.h b/applets/notes/plugin/abstractnoteloader.h new file mode 100644 index 00000000..94fd64b5 --- /dev/null +++ b/applets/notes/plugin/abstractnoteloader.h @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: 2014 David Edmundson + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + */ + +#ifndef ABSTRACTNOTELOADER_H +#define ABSTRACTNOTELOADER_H +#include + +class QString; +class Note; + +class AbstractNoteLoader +{ +public: + explicit AbstractNoteLoader(); + virtual ~AbstractNoteLoader(); + + virtual QStringList allNoteIds() = 0; + virtual Note *loadNote(const QString &id) = 0; + virtual void deleteNoteResources(const QString &id) = 0; + +private: +}; + +#endif // ABSTRACTNOTEMANAGER_H diff --git a/applets/notes/plugin/documenthandler.cpp b/applets/notes/plugin/documenthandler.cpp new file mode 100644 index 00000000..114bce1f --- /dev/null +++ b/applets/notes/plugin/documenthandler.cpp @@ -0,0 +1,371 @@ +/* + This file is part of the Qt Quick Controls module of the Qt Toolkit. + SPDX-FileCopyrightText: 2013 Digia Plc and /or its subsidiary(-ies) + + SPDX-License-Identifier: BSD-3-Clause + */ + +/* + * This file was imported from Qt into Plasma + * I expect this to become public API at some point + * at which point we should delete this + */ + +#include "documenthandler.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +DocumentHandler::DocumentHandler() + : m_target(nullptr) + , m_doc(nullptr) + , m_cursorPosition(-1) + , m_selectionStart(0) + , m_selectionEnd(0) +{ +} + +void DocumentHandler::setTarget(QQuickItem *target) +{ + m_doc = nullptr; + m_target = target; + if (!m_target) { + return; + } + + QVariant doc = m_target->property("textDocument"); + if (doc.canConvert()) { + QQuickTextDocument *qqdoc = doc.value(); + if (qqdoc) { + m_doc = qqdoc->textDocument(); + } + } + Q_EMIT targetChanged(); +} + +QString DocumentHandler::documentTitle() const +{ + return m_documentTitle; +} + +void DocumentHandler::setDocumentTitle(QString arg) +{ + if (m_documentTitle != arg) { + m_documentTitle = arg; + Q_EMIT documentTitleChanged(); + } +} + +void DocumentHandler::pasteWithoutFormatting() +{ + QTextCursor cursor = textCursor(); + if (cursor.isNull()) { + return; + } + + QClipboard *clipboard = QGuiApplication::clipboard(); + if (!clipboard) { + return; + } + + const QMimeData *mimeData = clipboard->mimeData(); + if (!mimeData) { + return; + } + + QString content = KTextToHTML::convertToHtml(mimeData->text(), KTextToHTML::Options(KTextToHTML::PreserveSpaces)); + + if (content.endsWith("", Qt::CaseInsensitive)) { + content += " "; + } + + cursor.insertHtml(content); +} + +void DocumentHandler::setText(const QString &arg) +{ + if (m_text != arg) { + m_text = arg; + Q_EMIT textChanged(); + } +} + +QString DocumentHandler::text() const +{ + return m_text; +} + +void DocumentHandler::setCursorPosition(int position) +{ + if (position == m_cursorPosition) { + return; + } + + m_cursorPosition = position; + + reset(); +} + +void DocumentHandler::reset() +{ + Q_EMIT fontFamilyChanged(); + Q_EMIT alignmentChanged(); + Q_EMIT boldChanged(); + Q_EMIT italicChanged(); + Q_EMIT underlineChanged(); + Q_EMIT strikeOutChanged(); + Q_EMIT fontSizeChanged(); + Q_EMIT textColorChanged(); +} + +QTextCursor DocumentHandler::textCursor() const +{ + if (!m_doc) { + return QTextCursor(); + } + QTextCursor cursor = QTextCursor(m_doc); + if (m_selectionStart != m_selectionEnd) { + cursor.setPosition(m_selectionStart); + cursor.setPosition(m_selectionEnd, QTextCursor::KeepAnchor); + } else { + cursor.setPosition(m_cursorPosition); + } + return cursor; +} + +void DocumentHandler::mergeFormatOnWordOrSelection(const QTextCharFormat &format) +{ + QTextCursor cursor = textCursor(); + if (!cursor.hasSelection()) { + cursor.select(QTextCursor::WordUnderCursor); + } + cursor.mergeCharFormat(format); +} + +void DocumentHandler::setSelectionStart(int position) +{ + m_selectionStart = position; +} + +void DocumentHandler::setSelectionEnd(int position) +{ + m_selectionEnd = position; +} + +void DocumentHandler::setAlignment(Qt::Alignment a) +{ + QTextBlockFormat fmt; + fmt.setAlignment((Qt::Alignment)a); + QTextCursor cursor = QTextCursor(m_doc); + cursor.setPosition(m_selectionStart, QTextCursor::MoveAnchor); + cursor.setPosition(m_selectionEnd, QTextCursor::KeepAnchor); + cursor.mergeBlockFormat(fmt); + Q_EMIT alignmentChanged(); +} + +Qt::Alignment DocumentHandler::alignment() const +{ + QTextCursor cursor = textCursor(); + if (cursor.isNull()) { + return Qt::AlignLeft; + } + return textCursor().blockFormat().alignment(); +} + +bool DocumentHandler::bold() const +{ + QTextCursor cursor = textCursor(); + if (cursor.isNull()) { + return false; + } + return textCursor().charFormat().fontWeight() == QFont::Bold; +} + +bool DocumentHandler::italic() const +{ + QTextCursor cursor = textCursor(); + if (cursor.isNull()) { + return false; + } + return textCursor().charFormat().fontItalic(); +} + +bool DocumentHandler::underline() const +{ + QTextCursor cursor = textCursor(); + if (cursor.isNull()) { + return false; + } + return textCursor().charFormat().fontUnderline(); +} + +bool DocumentHandler::strikeOut() const +{ + QTextCursor cursor = textCursor(); + if (cursor.isNull()) { + return false; + } + return textCursor().charFormat().fontStrikeOut(); +} + +void DocumentHandler::setBold(bool arg) +{ + QTextCharFormat fmt; + fmt.setFontWeight(arg ? QFont::Bold : QFont::Normal); + mergeFormatOnWordOrSelection(fmt); + Q_EMIT boldChanged(); +} + +void DocumentHandler::setItalic(bool arg) +{ + QTextCharFormat fmt; + fmt.setFontItalic(arg); + mergeFormatOnWordOrSelection(fmt); + Q_EMIT italicChanged(); +} + +void DocumentHandler::setUnderline(bool arg) +{ + QTextCharFormat fmt; + fmt.setFontUnderline(arg); + mergeFormatOnWordOrSelection(fmt); + Q_EMIT underlineChanged(); +} + +void DocumentHandler::setStrikeOut(bool arg) +{ + QTextCharFormat fmt; + fmt.setFontStrikeOut(arg); + mergeFormatOnWordOrSelection(fmt); + Q_EMIT strikeOutChanged(); +} + +int DocumentHandler::fontSize() const +{ + QTextCursor cursor = textCursor(); + if (cursor.isNull()) { + return 0; + } + QTextCharFormat format = cursor.charFormat(); + return format.font().pointSize(); +} + +void DocumentHandler::setFontSize(int arg) +{ + QTextCursor cursor = textCursor(); + if (cursor.isNull()) { + return; + } + QTextCharFormat format; + format.setFontPointSize(arg); + mergeFormatOnWordOrSelection(format); + // Also set the block char format, otherwise the old block font will stay and + // end up taking effect if the user deletes all the text. + cursor.mergeBlockCharFormat(format); + + Q_EMIT fontSizeChanged(); +} + +int DocumentHandler::defaultFontSize() const +{ + return m_doc ? m_doc->defaultFont().pointSize() : 0; +} + +void DocumentHandler::setDefaultFontSize(int arg) +{ + if (!m_doc) { + return; + } + + auto font = m_doc->defaultFont(); + font.setPointSize(arg); + m_doc->setDefaultFont(font); + Q_EMIT defaultFontSizeChanged(); +} + +QColor DocumentHandler::textColor() const +{ + QTextCursor cursor = textCursor(); + if (cursor.isNull()) { + return QColor(Qt::black); + } + QTextCharFormat format = cursor.charFormat(); + return format.foreground().color(); +} + +void DocumentHandler::setTextColor(const QColor &c) +{ + QTextCursor cursor = textCursor(); + if (cursor.isNull()) { + return; + } + QTextCharFormat format; + format.setForeground(QBrush(c)); + mergeFormatOnWordOrSelection(format); + Q_EMIT textColorChanged(); +} + +QString DocumentHandler::fontFamily() const +{ + QTextCursor cursor = textCursor(); + if (cursor.isNull()) { + return QString(); + } + QTextCharFormat format = cursor.charFormat(); + return format.font().family(); +} + +void DocumentHandler::setFontFamily(const QString &arg) +{ + QTextCursor cursor = textCursor(); + if (cursor.isNull()) { + return; + } + QTextCharFormat format; + format.setFontFamilies({arg}); + mergeFormatOnWordOrSelection(format); + Q_EMIT fontFamilyChanged(); +} + +QStringList DocumentHandler::defaultFontSizes() const +{ + // uhm... this is quite ugly + QStringList sizes; + const QList standardSizes = QFontDatabase::standardSizes(); + sizes.reserve(standardSizes.count()); + for (int size : standardSizes) { + sizes.append(QString::number(size)); + } + return sizes; +} + +QString DocumentHandler::strip(const QString text) +{ + return QTextDocumentFragment::fromHtml(text).toPlainText(); +} + +QString DocumentHandler::stripAndSimplify(const QString text) +{ + QString myText = text; + QString plainText = QTextDocumentFragment::fromHtml(myText).toPlainText(); + // Normalize all whitespace to increase the fuzziness of the match + plainText = plainText.simplified(); + return plainText; +} + +QString DocumentHandler::strippedClipboardText() +{ + QClipboard *clipboard = QGuiApplication::clipboard(); + if (!clipboard) { + return QString(); + } + + return stripAndSimplify(clipboard->text()); +} diff --git a/applets/notes/plugin/documenthandler.h b/applets/notes/plugin/documenthandler.h new file mode 100644 index 00000000..c4f186d7 --- /dev/null +++ b/applets/notes/plugin/documenthandler.h @@ -0,0 +1,154 @@ +/* + This file is part of the Qt Quick Controls module of the Qt Toolkit. + SPDX-FileCopyrightText: 2013 Digia Plc and /or its subsidiary(-ies) + + SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef DOCUMENTHANDLER_H +#define DOCUMENTHANDLER_H + +#include +#include + +QT_BEGIN_NAMESPACE +class QTextDocument; +QT_END_NAMESPACE + +class DocumentHandler : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QQuickItem *target READ target WRITE setTarget NOTIFY targetChanged) + Q_PROPERTY(int cursorPosition READ cursorPosition WRITE setCursorPosition NOTIFY cursorPositionChanged) + Q_PROPERTY(int selectionStart READ selectionStart WRITE setSelectionStart NOTIFY selectionStartChanged) + Q_PROPERTY(int selectionEnd READ selectionEnd WRITE setSelectionEnd NOTIFY selectionEndChanged) + + Q_PROPERTY(QColor textColor READ textColor WRITE setTextColor NOTIFY textColorChanged) + Q_PROPERTY(QString fontFamily READ fontFamily WRITE setFontFamily NOTIFY fontFamilyChanged) + Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment NOTIFY alignmentChanged) + + Q_PROPERTY(bool bold READ bold WRITE setBold NOTIFY boldChanged) + Q_PROPERTY(bool italic READ italic WRITE setItalic NOTIFY italicChanged) + Q_PROPERTY(bool underline READ underline WRITE setUnderline NOTIFY underlineChanged) + Q_PROPERTY(bool strikeOut READ strikeOut WRITE setStrikeOut NOTIFY strikeOutChanged) + + Q_PROPERTY(int fontSize READ fontSize WRITE setFontSize NOTIFY fontSizeChanged) + Q_PROPERTY(int defaultFontSize READ defaultFontSize WRITE setDefaultFontSize NOTIFY defaultFontSizeChanged) + + Q_PROPERTY(QStringList defaultFontSizes READ defaultFontSizes NOTIFY defaultFontSizesChanged) + + Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged) + Q_PROPERTY(QString documentTitle READ documentTitle WRITE setDocumentTitle NOTIFY documentTitleChanged) + +public: + DocumentHandler(); + + QQuickItem *target() + { + return m_target; + } + + void setTarget(QQuickItem *target); + + void setCursorPosition(int position); + void setSelectionStart(int position); + void setSelectionEnd(int position); + + int cursorPosition() const + { + return m_cursorPosition; + } + + int selectionStart() const + { + return m_selectionStart; + } + + int selectionEnd() const + { + return m_selectionEnd; + } + + QString fontFamily() const; + + QColor textColor() const; + + Qt::Alignment alignment() const; + void setAlignment(Qt::Alignment a); + + bool bold() const; + bool italic() const; + bool underline() const; + bool strikeOut() const; + int fontSize() const; + int defaultFontSize() const; + + QStringList defaultFontSizes() const; + QString text() const; + + QString documentTitle() const; + + Q_INVOKABLE QString strip(const QString text); + Q_INVOKABLE QString stripAndSimplify(const QString text); + Q_INVOKABLE QString strippedClipboardText(); + +public Q_SLOTS: + void setBold(bool arg); + void setItalic(bool arg); + void setUnderline(bool arg); + void setStrikeOut(bool arg); + void setFontSize(int arg); + void setDefaultFontSize(int arg); + void setTextColor(const QColor &arg); + void setFontFamily(const QString &arg); + + void setText(const QString &arg); + + void setDocumentTitle(QString arg); + + void pasteWithoutFormatting(); + void reset(); + +Q_SIGNALS: + void targetChanged(); + void cursorPositionChanged(); + void selectionStartChanged(); + void selectionEndChanged(); + + void fontFamilyChanged(); + void textColorChanged(); + void alignmentChanged(); + + void boldChanged(); + void italicChanged(); + void underlineChanged(); + void strikeOutChanged(); + + void fontSizeChanged(); + void defaultFontSizeChanged(); + void defaultFontSizesChanged(); + + void fileUrlChanged(); + + void textChanged(); + void documentTitleChanged(); + +private: + QTextCursor textCursor() const; + void mergeFormatOnWordOrSelection(const QTextCharFormat &format); + + QQuickItem *m_target; + QTextDocument *m_doc; + + int m_cursorPosition; + int m_selectionStart; + int m_selectionEnd; + + QFont m_font; + int m_fontSize; + QString m_text; + QString m_documentTitle; +}; + +#endif diff --git a/applets/notes/plugin/filesystemnoteloader.cpp b/applets/notes/plugin/filesystemnoteloader.cpp new file mode 100644 index 00000000..09b0ad5f --- /dev/null +++ b/applets/notes/plugin/filesystemnoteloader.cpp @@ -0,0 +1,108 @@ +/* + * SPDX-FileCopyrightText: 2014 David Edmundson + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + */ + +#include "filesystemnoteloader.h" + +#include "note.h" + +#include +#include +#include + +#include + +class FileNote : public Note +{ + Q_OBJECT +public: + FileNote(const QString &path, const QString &id); + void load(); + void save(const QString &text) override; + +private: + void fileSystemChanged(const QString &path); + QString m_path; + KDirWatch *m_watcher; +}; + +FileSystemNoteLoader::FileSystemNoteLoader() +{ + const QString genericDataLocation = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); + const QString suffix = QStringLiteral("plasma_notes"); + QDir(genericDataLocation).mkdir(suffix); + m_notesDir.setPath(genericDataLocation + QDir::separator() + suffix); +} + +QStringList FileSystemNoteLoader::allNoteIds() +{ + return m_notesDir.entryList(QStringList{QStringLiteral("*.txt")}); +} + +void FileSystemNoteLoader::deleteNoteResources(const QString &id) +{ + m_notesDir.remove(id); +} + +Note *FileSystemNoteLoader::loadNote(const QString &id) +{ + QString idToUse = id; + if (id.isEmpty()) { + idToUse = QUuid::createUuid().toString().mid(1, 34); // UUID adds random braces I don't want them on my file system + } + + FileNote *note = new FileNote(m_notesDir.absoluteFilePath(idToUse), idToUse); + return note; +} + +FileNote::FileNote(const QString &path, const QString &id) + : Note(id) + , m_path(path) + , m_watcher(new KDirWatch(this)) +{ + m_watcher->addFile(path); + + connect(m_watcher, &KDirWatch::created, this, &FileNote::fileSystemChanged); + connect(m_watcher, &KDirWatch::dirty, this, &FileNote::fileSystemChanged); + + load(); +} + +void FileNote::load() +{ + QFile file(m_path); + if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { + setNoteText(QString::fromUtf8(file.readAll())); + } +} + +void FileNote::save(const QString &text) +{ + if (text == noteText()) { + return; + } + + m_watcher->removeFile(m_path); + + QFile file(m_path); + if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { + file.write(text.toUtf8()); + } else { + qWarning() << "Could not write notes to file" << m_path; + } + setNoteText(text); + + m_watcher->addFile(m_path); +} + +void FileNote::fileSystemChanged(const QString &path) +{ + if (path == m_path) { + load(); + } +} + +#include "filesystemnoteloader.moc" diff --git a/applets/notes/plugin/filesystemnoteloader.h b/applets/notes/plugin/filesystemnoteloader.h new file mode 100644 index 00000000..5f7e3c92 --- /dev/null +++ b/applets/notes/plugin/filesystemnoteloader.h @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: 2014 David Edmundson + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + */ + +#ifndef FILESYSTEMNOTELOADER_H +#define FILESYSTEMNOTELOADER_H + +#include "abstractnoteloader.h" + +#include +#include + +class FileSystemNoteLoader : public AbstractNoteLoader +{ +public: + explicit FileSystemNoteLoader(); + QStringList allNoteIds() override; + Note *loadNote(const QString &id) override; + void deleteNoteResources(const QString &id) override; + +private: + QDir m_notesDir; +}; + +#endif // FILESYSTEMNOTEMANAGER_H diff --git a/applets/notes/plugin/note.cpp b/applets/notes/plugin/note.cpp new file mode 100644 index 00000000..d7a1cc17 --- /dev/null +++ b/applets/notes/plugin/note.cpp @@ -0,0 +1,33 @@ +/* + * SPDX-FileCopyrightText: 2014 David Edmundson + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + */ + +#include "note.h" + +Note::Note(const QString &id) + : QObject() + , m_id(id) +{ +} + +QString Note::id() const +{ + return m_id; +} + +void Note::setNoteText(const QString &text) +{ + if (text == m_noteText) { + return; + } + m_noteText = text; + Q_EMIT noteTextChanged(); +} + +QString Note::noteText() const +{ + return m_noteText; +} diff --git a/applets/notes/plugin/note.h b/applets/notes/plugin/note.h new file mode 100644 index 00000000..8adf4421 --- /dev/null +++ b/applets/notes/plugin/note.h @@ -0,0 +1,42 @@ +/* + * SPDX-FileCopyrightText: 2014 David Edmundson + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + */ + +#ifndef NOTE_H +#define NOTE_H + +#include + +class Note : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString id READ id CONSTANT) + Q_PROPERTY(QString noteText READ noteText NOTIFY noteTextChanged) + +public: + explicit Note(const QString &id); + QString id() const; + + // what's in the plasmoid + // backends save this and write into storedText + QString noteText() const; + void setNoteText(const QString &text); + +public Q_SLOTS: + virtual void save(const QString &text) = 0; + + // FUTURE + // status None, Ready, Loading, Error + +Q_SIGNALS: + void noteTextChanged(); + +private: + const QString m_id; + QString m_noteText; +}; + +#endif // NOTE_H diff --git a/applets/notes/plugin/notemanager.cpp b/applets/notes/plugin/notemanager.cpp new file mode 100644 index 00000000..d186a16b --- /dev/null +++ b/applets/notes/plugin/notemanager.cpp @@ -0,0 +1,48 @@ +/* + * SPDX-FileCopyrightText: 2014 David Edmundson + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + */ + +#include "notemanager.h" + +#include +#include +#include + +#include "filesystemnoteloader.h" +#include "note.h" + +NoteManager::NoteManager(QObject *parent) + : QObject(parent) + , m_backend(loadBackend()) +{ +} + +Note *NoteManager::loadNote(const QString &id) +{ + Note *note = m_backend->loadNote(id); + QQmlEngine::setObjectOwnership(note, QQmlEngine::JavaScriptOwnership); + return note; +} + +void NoteManager::deleteNoteResources(const QString &id) +{ + m_backend->deleteNoteResources(id); +} + +QSharedPointer NoteManager::loadBackend() +{ + static QMutex mutex; + static QWeakPointer s_backend; + + mutex.lock(); + QSharedPointer manager = s_backend.toStrongRef(); + if (manager.isNull()) { + manager.reset(new FileSystemNoteLoader); + s_backend = manager; + } + mutex.unlock(); + return manager; +} diff --git a/applets/notes/plugin/notemanager.h b/applets/notes/plugin/notemanager.h new file mode 100644 index 00000000..775bf992 --- /dev/null +++ b/applets/notes/plugin/notemanager.h @@ -0,0 +1,46 @@ +/* + * SPDX-FileCopyrightText: 2014 David Edmundson + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + */ + +#ifndef NOTEMANAGER_H +#define NOTEMANAGER_H + +#include +#include +#include + +#include "abstractnoteloader.h" +class Note; + +class NoteManager : public QObject +{ + Q_OBJECT + +public: + explicit NoteManager(QObject *parent = nullptr); + + /** + * Loads the note for the ID given + * Ownership is passed to the QML context + */ + Q_INVOKABLE Note *loadNote(const QString &id); + + /** + * Remove any resources associated with the note ID + */ + Q_INVOKABLE void deleteNoteResources(const QString &id); + + // LATER QAbstractListModel* notesModel(); //list of all notes + +private: + // ref count backends so that we only have for all notes + static QSharedPointer loadBackend(); + + QSharedPointer m_backend; + QWeakPointer m_lastNote; +}; + +#endif // NOTEMANAGER_H diff --git a/applets/notes/plugin/notesplugin.cpp b/applets/notes/plugin/notesplugin.cpp new file mode 100644 index 00000000..ceba16ba --- /dev/null +++ b/applets/notes/plugin/notesplugin.cpp @@ -0,0 +1,62 @@ +/* + SPDX-FileCopyrightText: 2014 David Edmundson + SPDX-FileCopyrightText: 2015 Kai Uwe Broulik + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "documenthandler.h" +#include "note.h" +#include "notemanager.h" + +#include +#include +#include + +class NotesHelper : public QObject +{ + Q_OBJECT + +public: + explicit NotesHelper(QObject *parent = nullptr) + : QObject(parent) + { + } + + ~NotesHelper() override = default; + + Q_INVOKABLE QString fileContents(const QString &path) const + { + const QUrl &url = QUrl::fromUserInput(path); + if (!url.isValid()) { + return QString(); + } + + QFile file(url.toLocalFile()); + if (!file.open(QIODevice::ReadOnly)) { + return QString(); + } + + return QString::fromUtf8(file.readAll()); + } +}; + +class NotesPlugin : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") +public: + void registerTypes(const char *uri) override + + { + Q_ASSERT(QLatin1String(uri) == QLatin1String("org.kde.plasma.private.notes")); + qmlRegisterType(uri, 0, 1, "DocumentHandler"); + qmlRegisterType(uri, 0, 1, "NoteManager"); + qmlRegisterUncreatableType(uri, 0, 1, "Note", QStringLiteral("Create through NoteManager")); + qmlRegisterSingletonType(uri, 0, 1, "NotesHelper", [](QQmlEngine *, QJSEngine *) { + return new NotesHelper(); + }); + } +}; + +#include "notesplugin.moc" diff --git a/applets/quicklaunch/CMakeLists.txt b/applets/quicklaunch/CMakeLists.txt new file mode 100644 index 00000000..c765a629 --- /dev/null +++ b/applets/quicklaunch/CMakeLists.txt @@ -0,0 +1,3 @@ +add_subdirectory(plugin) + +plasma_install_package(package org.kde.plasma.quicklaunch) diff --git a/applets/quicklaunch/Messages.sh b/applets/quicklaunch/Messages.sh new file mode 100644 index 00000000..bb370b87 --- /dev/null +++ b/applets/quicklaunch/Messages.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +$XGETTEXT `find . -name '*.js' -o -name '*.qml' -o -name '*.cpp'` -o $podir/plasma_applet_org.kde.plasma.quicklaunch.pot diff --git a/applets/quicklaunch/package/contents/config/config.qml b/applets/quicklaunch/package/contents/config/config.qml new file mode 100644 index 00000000..55eb94bf --- /dev/null +++ b/applets/quicklaunch/package/contents/config/config.qml @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: 2015 David Rosca + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + */ + +import QtQuick 2.0 +import org.kde.plasma.configuration 2.0 + +ConfigModel { + ConfigCategory { + name: i18nc("@title", "General") + icon: "plasma" + source: "ConfigGeneral.qml" + } +} diff --git a/applets/quicklaunch/package/contents/config/main.xml b/applets/quicklaunch/package/contents/config/main.xml new file mode 100644 index 00000000..71528c90 --- /dev/null +++ b/applets/quicklaunch/package/contents/config/main.xml @@ -0,0 +1,29 @@ + + + + + + + 1 + + + false + + + false + + + + + + + + + + + + + diff --git a/applets/quicklaunch/package/contents/ui/ConfigGeneral.qml b/applets/quicklaunch/package/contents/ui/ConfigGeneral.qml new file mode 100644 index 00000000..f95d30c8 --- /dev/null +++ b/applets/quicklaunch/package/contents/ui/ConfigGeneral.qml @@ -0,0 +1,92 @@ +/* + * SPDX-FileCopyrightText: 2015 David Rosca + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + */ + +import QtQuick 2.5 +import QtQuick.Layouts 1.3 +import QtQuick.Controls 2.5 as QQC2 + +import org.kde.plasma.core as PlasmaCore +import org.kde.kirigami 2.20 as Kirigami +import org.kde.kcmutils as KCM + +KCM.SimpleKCM { + readonly property bool vertical: plasmoid.formFactor == PlasmaCore.Types.Vertical || (plasmoid.formFactor == PlasmaCore.Types.Planar && plasmoid.height > plasmoid.width) + + property alias cfg_maxSectionCount: maxSectionCount.value + property alias cfg_showLauncherNames: showLauncherNames.checked + property alias cfg_enablePopup: enablePopup.checked + property alias cfg_title: title.text + + Kirigami.FormLayout { + QQC2.SpinBox { + id: maxSectionCount + + Kirigami.FormData.label: vertical ? i18nc("@label:spinbox", "Maximum columns:") : i18nc("@label:spinbox", "Maximum rows:") + + from: 1 + } + + Item { + Kirigami.FormData.isSection: true + } + + QQC2.CheckBox { + id: showLauncherNames + + Kirigami.FormData.label: i18nc("@title:group", "Appearance:") + + text: i18nc("@option:check", "Show launcher names") + } + + QQC2.CheckBox { + id: enablePopup + text: i18nc("@option:check", "Enable popup") + } + + + Item { + Kirigami.FormData.isSection: true + } + + + RowLayout { + Kirigami.FormData.label: i18nc("@title:group", "Title:") + Layout.fillWidth: true + + visible: plasmoid.formFactor == PlasmaCore.Types.Planar + + QQC2.CheckBox { + id: showTitle + checked: title.length + text: i18nc("@option:check", "Show:") + + onClicked: { + if (checked) { + title.forceActiveFocus(); + } else { + title.text = ""; + } + } + } + + Kirigami.ActionTextField { + id: title + enabled: showTitle.checked + + Layout.fillWidth: true + placeholderText: i18nc("@info:placeholder", "Custom title") + + rightActions: [ + Kirigami.Action { + icon.name: "edit-clear" + visible: title.text.length !== 0 + onTriggered: title.text = ""; + } + ] + } + } + } +} diff --git a/applets/quicklaunch/package/contents/ui/IconItem.qml b/applets/quicklaunch/package/contents/ui/IconItem.qml new file mode 100644 index 00000000..e6ae4486 --- /dev/null +++ b/applets/quicklaunch/package/contents/ui/IconItem.qml @@ -0,0 +1,402 @@ +/* + * SPDX-FileCopyrightText: 2015 David Rosca + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + */ + +import QtQuick 2.15 +import org.kde.plasma.core as PlasmaCore +import org.kde.kirigami 2.20 as Kirigami +import org.kde.ksvg 1.0 as KSvg +import org.kde.plasma.components 3.0 as PlasmaComponents3 +import org.kde.plasma.plasmoid 2.0 +import org.kde.draganddrop 2.0 as DragAndDrop +import org.kde.plasma.extras 2.0 as PlasmaExtras + +import "layout.js" as LayoutManager + +Item { + id: iconItem + + readonly property int itemIndex : index + property bool dragging : false + property bool isPopupItem : false + readonly property var launcher : logic.launcherData(url) + readonly property string iconName : launcher.iconName || "fork" + + width: isPopupItem ? LayoutManager.popupItemWidth() : grid.cellWidth + height: isPopupItem ? LayoutManager.popupItemHeight() : grid.cellHeight + + Keys.onPressed: { + switch (event.key) { + case Qt.Key_Space: + case Qt.Key_Enter: + case Qt.Key_Return: + case Qt.Key_Select: + logic.openUrl(url); + break; + case Qt.Key_Menu: + contextMenu.refreshActions(); + contextMenu.open(0,0); + event.accepted = true; + break; + case Qt.Key_Backspace: + case Qt.Key_Delete: + removeLauncher(); + event.accepted = true; + break; + } + + // BEGIN Arrow keys + if (!(event.modifiers & Qt.ControlModifier) || !(event.modifiers & Qt.ShiftModifier)) { + return; + } + + switch (event.key) { + case Qt.Key_Up: { + if (iconItem.isPopupItem && iconItem.itemIndex === 0 && Plasmoid.location === PlasmaCore.Types.TopEdge) { + iconItem.ListView.view.moveItemToGrid(iconItem, url); + break; + } else if (!iconItem.isPopupItem && Plasmoid.location === PlasmaCore.Types.BottomEdge) { + iconItem.GridView.view.moveItemToPopup(iconItem, url); + break; + } + + decreaseIndex(); + break; + } + + case Qt.Key_Down: { + if (iconItem.isPopupItem && iconItem.itemIndex === iconItem.ListView.view.count - 1 && Plasmoid.location === PlasmaCore.Types.BottomEdge) { + iconItem.ListView.view.moveItemToGrid(iconItem, url); + break; + } else if (!iconItem.isPopupItem && Plasmoid.location === PlasmaCore.Types.TopEdge) { + iconItem.GridView.view.moveItemToPopup(iconItem, url); + break; + } + + increaseIndex(); + break; + } + + case Qt.Key_Left: { + if (iconItem.isPopupItem && Plasmoid.location === PlasmaCore.Types.LeftEdge) { + iconItem.ListView.view.moveItemToGrid(iconItem, url); + break; + } else if (!iconItem.isPopupItem && Plasmoid.location === PlasmaCore.Types.RightEdge) { + iconItem.GridView.view.moveItemToPopup(iconItem, url); + break; + } + + decreaseIndex(); + break; + } + case Qt.Key_Right: { + if (iconItem.isPopupItem && Plasmoid.location === PlasmaCore.Types.RightEdge) { + iconItem.ListView.view.moveItemToGrid(iconItem, url); + break; + } else if (!iconItem.isPopupItem && Plasmoid.location === PlasmaCore.Types.LeftEdge) { + iconItem.GridView.view.moveItemToPopup(iconItem, url); + break; + } + + increaseIndex(); + break; + } + default: + return; + } + + event.accepted = true; + // END Arrow keys + } + + function decreaseIndex() { + const newIndex = iconItem.itemIndex - 1; + if (newIndex < 0) { + return; + } + if (iconItem.isPopupItem) { + popupModel.moveUrl(iconItem.itemIndex, newIndex); + iconItem.ListView.view.currentIndex = newIndex; + } else { + launcherModel.moveUrl(iconItem.itemIndex, newIndex); + iconItem.GridView.view.currentIndex = newIndex; + } + } + + function increaseIndex() { + const newIndex = iconItem.itemIndex + 1; + if (newIndex === (iconItem.isPopupItem ? iconItem.ListView.view.count : iconItem.GridView.view.count)) { + return; + } + if (iconItem.isPopupItem) { + popupModel.moveUrl(iconItem.itemIndex, newIndex); + iconItem.ListView.view.currentIndex = newIndex; + } else { + launcherModel.moveUrl(iconItem.itemIndex, newIndex); + iconItem.GridView.view.currentIndex = newIndex; + } + } + + DragAndDrop.DragArea { + id: dragArea + width: Math.min(iconItem.width, iconItem.height) + height: width + enabled: !plasmoid.immutable + defaultAction: Qt.MoveAction + supportedActions: Qt.IgnoreAction | Qt.MoveAction + delegate: icon + + mimeData { + url: url + source: iconItem + } + + onDragStarted: { + dragging = true; + } + + onDrop: { + dragging = false; + + if (action == Qt.MoveAction) { + removeLauncher(); + } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + anchors.margins: LayoutManager.itemPadding() + hoverEnabled: true + acceptedButtons: Qt.LeftButton | Qt.RightButton + + activeFocusOnTab: true + Accessible.name: iconItem.launcher.applicationName + Accessible.description: i18n("Launch %1", iconItem.launcher.genericName || iconItem.launcher.applicationName) + Accessible.role: Accessible.Button + + onActiveFocusChanged: { + if (activeFocus) { + entered(); + } + } + + onEntered: { + if (iconItem.ListView.view) { + iconItem.ListView.view.currentIndex = iconItem.itemIndex; + } + } + + onPressed: { + if (mouse.button == Qt.RightButton) { + contextMenu.refreshActions(); + contextMenu.open(mouse.x, mouse.y); + } + } + + onClicked: { + if (mouse.button == Qt.LeftButton) { + logic.openUrl(url) + } + } + + Kirigami.Icon { + id: icon + + anchors { + top: parent.top + left: parent.left + } + + width: Kirigami.Units.iconSizes.medium + height: width + source: url == "quicklaunch:drop" ? "" : iconName + active: mouseArea.containsMouse + } + + PlasmaComponents3.Label { + id: label + + anchors { + bottom : parent.bottom + right : parent.right + } + + text: iconItem.launcher.applicationName + textFormat: Text.PlainText + maximumLineCount: 1 + wrapMode: Text.Wrap + } + + KSvg.FrameSvgItem { + anchors.fill: parent + imagePath: "widgets/viewitem" + prefix: "hover" + visible: dragging || url == "quicklaunch:drop" + } + + PlasmaCore.ToolTipArea { + anchors.fill: parent + active: !dragging + mainText: iconItem.launcher.applicationName + subText: iconItem.launcher.genericName + icon: iconName + } + + PlasmaExtras.Menu { + id: contextMenu + + property var jumpListItems : [] + + visualParent: mouseArea + + PlasmaExtras.MenuItem { + id: jumpListSeparator + separator: true + } + + PlasmaExtras.MenuItem { + text: i18nc("@action:inmenu", "Add Launcher…") + icon: "list-add" + onClicked: addLauncher() + } + + PlasmaExtras.MenuItem { + text: i18nc("@action:inmenu", "Edit Launcher…") + icon: "document-edit" + onClicked: editLauncher() + } + + PlasmaExtras.MenuItem { + text: i18nc("@action:inmenu", "Remove Launcher") + icon: "list-remove" + onClicked: removeLauncher() + } + + PlasmaExtras.MenuItem { + separator: true + } + + PlasmaExtras.MenuItem { + action: Plasmoid.internalAction("configure") + } + + PlasmaExtras.MenuItem { + action: Plasmoid.internalAction("remove") + } + + function refreshActions() { + for (var i = 0; i < jumpListItems.length; ++i) { + var item = jumpListItems[i]; + removeMenuItem(item); + item.destroy(); + } + jumpListItems = []; + + for (var i = 0; i < launcher.jumpListActions.length; ++i) { + var action = launcher.jumpListActions[i]; + var item = menuItemComponent.createObject(iconItem, { + "text": action.name, + "icon": action.icon + }); + item.clicked.connect(function() { + logic.openExec(this.exec); + }.bind(action)); + + addMenuItem(item, jumpListSeparator); + jumpListItems.push(item); + } + } + } + + Component { + id: menuItemComponent + PlasmaExtras.MenuItem { } + } + } + } + + states: [ + State { + name: "popup" + when: isPopupItem + + AnchorChanges { + target: dragArea + anchors.left: dragArea.parent.left + anchors.right: dragArea.parent.right + anchors.top: dragArea.parent.top + anchors.bottom: dragArea.parent.bottom + } + + AnchorChanges { + target: icon + anchors.right: undefined + anchors.bottom: undefined + } + + AnchorChanges { + target: label + anchors.top: label.parent.top + anchors.left: icon.right + } + + PropertyChanges { + target: label + horizontalAlignment: Text.AlignHLeft + visible: true + elide: Text.ElideRight + anchors.leftMargin: Kirigami.Units.smallSpacing + anchors.rightMargin: Kirigami.Units.smallSpacing + } + }, + + State { + name: "grid" + when: !isPopupItem + + AnchorChanges { + target: dragArea + anchors.verticalCenter: dragArea.parent.verticalCenter + anchors.horizontalCenter: dragArea.parent.horizontalCenter + } + + AnchorChanges { + target: icon + anchors.right: icon.parent.right + anchors.bottom: label.visible ? label.top : icon.parent.bottom + } + + AnchorChanges { + target: label + anchors.top: undefined + anchors.left: label.parent.left + } + + PropertyChanges { + target: label + horizontalAlignment: Text.AlignHCenter + visible: showLauncherNames + elide: Text.ElideNone + } + } + ] + + function addLauncher() + { + logic.addLauncher(isPopupItem); + } + + function editLauncher() + { + logic.editLauncher(url, itemIndex, isPopupItem); + } + + function removeLauncher() + { + var m = isPopupItem ? popupModel : launcherModel; + m.removeUrl(itemIndex); + } +} diff --git a/applets/quicklaunch/package/contents/ui/Popup.qml b/applets/quicklaunch/package/contents/ui/Popup.qml new file mode 100644 index 00000000..3e436e3c --- /dev/null +++ b/applets/quicklaunch/package/contents/ui/Popup.qml @@ -0,0 +1,124 @@ +/* + * SPDX-FileCopyrightText: 2015 David Rosca + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + */ + +import QtQuick 2.2 + +import org.kde.kirigami 2.20 as Kirigami +import org.kde.plasma.extras 2.0 as PlasmaExtras + +import org.kde.draganddrop 2.0 as DragAndDrop + +import "layout.js" as LayoutManager + +Item { + id: popup + + property bool dragging: false + property alias popupModel : popupModel + property alias listView: listView + + width: LayoutManager.popupItemWidth() + height: Math.max(1, popupModel.count) * LayoutManager.popupItemHeight() + + DragAndDrop.DropArea { + anchors.fill: parent + preventStealing: true + enabled: !plasmoid.immutable + + onDragEnter: { + dragging = true; + } + + onDragMove: { + if (!event.mimeData.hasUrls) { + return; + } + + var index = listView.indexAt(event.x, event.y); + + if (isInternalDrop(event)) { + popupModel.moveUrl(event.mimeData.source.itemIndex, index); + } else if (event.mimeData.hasUrls) { + popupModel.showDropMarker(index); + } + } + + onDragLeave: { + dragging = false; + popupModel.clearDropMarker(); + } + + onDrop: { + dragging = false; + popupModel.clearDropMarker(); + + if (isInternalDrop(event)) { + event.accept(Qt.IgnoreAction); + saveConfiguration(); + } else if (event.mimeData.hasUrls) { + var index = listView.indexAt(event.x, event.y); + popupModel.insertUrls(index == -1 ? popupModel.count : index, event.mimeData.urls); + event.accept(event.proposedAction); + } + } + } + + ListView { + id: listView + anchors.fill: parent + + focus: true + interactive: true + keyNavigationWraps: true + + model: UrlModel { + id: popupModel + } + + delegate: IconItem { + isPopupItem: true + } + + highlight: PlasmaExtras.Highlight {} + + highlightMoveDuration: Kirigami.Units.longDuration + highlightMoveVelocity: 1 + + function moveItemToGrid(iconItem, url) { + launcherModel.insertUrl(launcherModel.count, url); + listView.currentIndex = launcherModel.count - 1; + iconItem.removeLauncher(); + } + } + + Connections { + target: plasmoid.configuration + function onPopupUrlsChanged() { + popupModel.urlsChanged.disconnect(saveConfiguration); + popupModel.setUrls(plasmoid.configuration.popupUrls); + popupModel.urlsChanged.connect(saveConfiguration); + } + } + + Component.onCompleted: { + popupModel.setUrls(plasmoid.configuration.popupUrls); + popupModel.urlsChanged.connect(saveConfiguration); + } + + function saveConfiguration() + { + if (!dragging) { + plasmoid.configuration.popupUrls = popupModel.urls(); + } + } + + function isInternalDrop(event) + { + return event.mimeData.source + && event.mimeData.source.ListView + && event.mimeData.source.ListView.view == listView; + } +} diff --git a/applets/quicklaunch/package/contents/ui/UrlModel.qml b/applets/quicklaunch/package/contents/ui/UrlModel.qml new file mode 100644 index 00000000..03607512 --- /dev/null +++ b/applets/quicklaunch/package/contents/ui/UrlModel.qml @@ -0,0 +1,109 @@ +/* + * SPDX-FileCopyrightText: 2015 David Rosca + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + */ + +import QtQuick 2.2 + +ListModel { + id: listModel + + property int dropMarkerIndex : -1 + + signal urlsChanged() + + function urls() + { + var out = []; + for (var i = 0; i < listModel.count; ++i) { + out.push(get(i).url); + } + return out; + } + + function setUrls(urls) + { + clear(); + insertUrls(0, urls); + + urlsChanged(); + } + + function appendUrl(url) + { + append({ url: url }); + + urlsChanged(); + } + + function insertUrl(index, url) + { + insert(index, { url: url }); + + urlsChanged(); + } + + function insertUrls(index, urls) + { + for (var i = 0; i < urls.length; ++i) { + insert(index + i, { url: urls[i] }); + } + + if (urls.length) { + urlsChanged(); + } + } + + function changeUrl(index, url) + { + // Force reloading delegate data + set(index, { url: "quicklaunch:empty" }); + set(index, { url: url }); + + urlsChanged(); + } + + function moveUrl(from, to) + { + if (from == -1 || to == -1 || from == to) { + return false; + } + + move(from, to, 1); + + urlsChanged(); + return true; + } + + function removeUrl(index) + { + remove(index, 1); + + urlsChanged(); + } + + // Drop marker is internally represented as "quicklaunch:drop" url + function showDropMarker(index) + { + if (index == -1) { + index = dropMarkerIndex == -1 ? count : count - 1; + } + + if (dropMarkerIndex != -1) { + move(dropMarkerIndex, index, 1); + dropMarkerIndex = index; + } else { + insert(index, { url: "quicklaunch:drop" }); + dropMarkerIndex = index; + } + } + + function clearDropMarker() + { + if (dropMarkerIndex != -1) { + remove(dropMarkerIndex, 1); + dropMarkerIndex = -1; + } + } +} diff --git a/applets/quicklaunch/package/contents/ui/layout.js b/applets/quicklaunch/package/contents/ui/layout.js new file mode 100644 index 00000000..b50b1144 --- /dev/null +++ b/applets/quicklaunch/package/contents/ui/layout.js @@ -0,0 +1,108 @@ +/* + * SPDX-FileCopyrightText: 2015 David Rosca + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + */ + +.import org.kde.plasma.core as PlasmaCore +.import org.kde.kirigami 2.20 as Kirigami + +function itemPadding() { return Kirigami.Units.smallSpacing / 2; } + +function rows() +{ + if (vertical) { + return Math.ceil(grid.count / maxSectionCount); + } + return Math.min(grid.count, maxSectionCount); +} + +function cols() +{ + if (vertical) { + return Math.min(grid.count, maxSectionCount); + } + return Math.ceil(grid.count / maxSectionCount); +} + +function minimumWidth() +{ + var w = cols() * minimumCellWidth(); + if (!vertical && popupArrow.visible) { + w += popupArrow.width; + } + return w; +} + +function minimumHeight() +{ + var h = rows() * minimumCellHeight(); + if (vertical && popupArrow.visible) { + h += popupArrow.height; + } + if (title.length) { + h += titleLabel.height; + } + return h; +} + +function preferredWidth() +{ + var w = cols() * preferredCellWidth(); + if (horizontal) { + w = (preferredHeight() / rows()) * cols(); + } + if (!vertical && popupArrow.visible) { + w += popupArrow.width; + } + return w; +} + +function preferredHeight() +{ + var h = rows() * preferredCellHeight(); + if (vertical) { + h = (preferredWidth() / cols()) * rows(); + if (popupArrow.visible) { + h += popupArrow.height; + } + } + if (title.length) { + h += titleLabel.height; + } + return h; +} + +function minimumCellWidth() +{ + return Kirigami.Units.iconSizes.small + 2 * itemPadding(); +} + +function minimumCellHeight() +{ + var h = Kirigami.Units.iconSizes.small + 2 * itemPadding(); + if (showLauncherNames) { + h += Kirigami.Units.gridUnit * 2; + } + return h; +} + +function preferredCellWidth() +{ + return Math.floor(grid.width / cols()); +} + +function preferredCellHeight() +{ + return Math.floor(grid.height / rows()); +} + +function popupItemWidth() +{ + return Math.max(root.width, Kirigami.Units.iconSizes.medium + 20 * Kirigami.Units.gridUnit); +} + +function popupItemHeight() +{ + return Kirigami.Units.iconSizes.medium + 2 * itemPadding(); +} diff --git a/applets/quicklaunch/package/contents/ui/main.qml b/applets/quicklaunch/package/contents/ui/main.qml new file mode 100644 index 00000000..c552c397 --- /dev/null +++ b/applets/quicklaunch/package/contents/ui/main.qml @@ -0,0 +1,305 @@ +/* + * SPDX-FileCopyrightText: 2015 David Rosca + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + */ + +import QtQuick 2.2 +import QtQuick.Layouts 1.0 +import org.kde.plasma.plasmoid 2.0 +import org.kde.plasma.core as PlasmaCore +import org.kde.kirigami 2.20 as Kirigami +import org.kde.plasma.components 3.0 as PlasmaComponents3 +import org.kde.draganddrop 2.0 as DragAndDrop +import org.kde.plasma.private.quicklaunch 1.0 + +import "layout.js" as LayoutManager + +PlasmoidItem { + id: root + + readonly property int maxSectionCount: plasmoid.configuration.maxSectionCount + readonly property bool showLauncherNames : plasmoid.configuration.showLauncherNames + readonly property bool enablePopup : plasmoid.configuration.enablePopup + readonly property string title : plasmoid.formFactor == PlasmaCore.Types.Planar ? plasmoid.configuration.title : "" + readonly property bool vertical : plasmoid.formFactor == PlasmaCore.Types.Vertical || (plasmoid.formFactor == PlasmaCore.Types.Planar && height > width) + readonly property bool horizontal : plasmoid.formFactor == PlasmaCore.Types.Horizontal + property bool dragging : false + + Layout.minimumWidth: LayoutManager.minimumWidth() + Layout.minimumHeight: LayoutManager.minimumHeight() + Layout.preferredWidth: LayoutManager.preferredWidth() + Layout.preferredHeight: LayoutManager.preferredHeight() + + preferredRepresentation: fullRepresentation + Plasmoid.backgroundHints: PlasmaCore.Types.DefaultBackground | PlasmaCore.Types.ConfigurableBackground + + Item { + anchors.fill: parent + + DragAndDrop.DropArea { + anchors.fill: parent + preventStealing: true + enabled: !plasmoid.immutable + + onDragEnter: { + if (event.mimeData.hasUrls) { + dragging = true; + } else { + event.ignore(); + } + } + + onDragMove: { + var index = grid.indexAt(event.x, event.y); + + if (isInternalDrop(event)) { + launcherModel.moveUrl(event.mimeData.source.itemIndex, index); + } else { + launcherModel.showDropMarker(index); + } + + popup.visible = root.childAt(event.x, event.y) == popupArrow; + } + + onDragLeave: { + dragging = false; + launcherModel.clearDropMarker(); + } + + onDrop: { + dragging = false; + launcherModel.clearDropMarker(); + + if (isInternalDrop(event)) { + event.accept(Qt.IgnoreAction); + saveConfiguration(); + } else { + var index = grid.indexAt(event.x, event.y); + launcherModel.insertUrls(index == -1 ? launcherModel.count : index, event.mimeData.urls); + event.accept(event.proposedAction); + } + } + } + + PlasmaComponents3.Label { + id: titleLabel + + anchors { + top: parent.top + left: parent.left + right: parent.right + } + + height: Kirigami.Units.iconSizes.sizeForLabels + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignTop + elide: Text.ElideMiddle + text: title + textFormat: Text.PlainText + } + + Item { + id: launcher + + anchors { + top: title.length ? titleLabel.bottom : parent.top + left: parent.left + right: !vertical && popupArrow.visible ? popupArrow.left : parent.right + bottom: vertical && popupArrow.visible ? popupArrow.top : parent.bottom + } + + GridView { + id: grid + anchors.fill: parent + interactive: false + flow: horizontal ? GridView.FlowTopToBottom : GridView.FlowLeftToRight + cellWidth: LayoutManager.preferredCellWidth() + cellHeight: LayoutManager.preferredCellHeight() + visible: count + + model: UrlModel { + id: launcherModel + } + + delegate: IconItem { } + + function moveItemToPopup(iconItem, url) { + if (!popupArrow.visible) { + return; + } + + popup.visible = true; + popup.mainItem.popupModel.insertUrl(popup.mainItem.popupModel.count, url); + popup.mainItem.listView.currentIndex = popup.mainItem.popupModel.count - 1; + iconItem.removeLauncher(); + } + } + + Kirigami.Icon { + id: defaultIcon + anchors.fill: parent + source: "fork" + visible: !grid.visible + + PlasmaCore.ToolTipArea { + anchors.fill: parent + mainText: i18n("Quicklaunch") + subText: i18nc("@info", "Add launchers by Drag and Drop or by using the context menu.") + location: Plasmoid.location + } + } + } + + PlasmaCore.Dialog { + id: popup + type: PlasmaCore.Dialog.PopupMenu + flags: Qt.WindowStaysOnTopHint + hideOnWindowDeactivate: true + location: plasmoid.location + visualParent: vertical ? popupArrow : root + + mainItem: Popup { + Keys.onEscapePressed: popup.visible = false + } + } + + PlasmaCore.ToolTipArea { + id: popupArrow + visible: enablePopup + location: Plasmoid.location + + anchors { + top: vertical ? undefined : parent.top + right: parent.right + bottom: parent.bottom + } + + subText: popup.visible ? i18n("Hide icons") : i18n("Show hidden icons") + + MouseArea { + id: arrowMouseArea + anchors.fill: parent + + activeFocusOnTab: parent.visible + + Keys.onPressed: { + switch (event.key) { + case Qt.Key_Space: + case Qt.Key_Enter: + case Qt.Key_Return: + case Qt.Key_Select: + arrowMouseArea.clicked(null); + break; + } + } + Accessible.name: parent.subText + Accessible.role: Accessible.Button + + onClicked: { + popup.visible = !popup.visible + } + + Kirigami.Icon { + anchors.fill: parent + + rotation: popup.visible ? 180 : 0 + Behavior on rotation { + RotationAnimation { + duration: Kirigami.Units.shortDuration * 3 + } + } + + source: { + if (plasmoid.location == PlasmaCore.Types.TopEdge) { + return "arrow-down"; + } else if (plasmoid.location == PlasmaCore.Types.LeftEdge) { + return "arrow-right"; + } else if (plasmoid.location == PlasmaCore.Types.RightEdge) { + return "arrow-left"; + } else if (vertical) { + return "arrow-right"; + } else { + return "arrow-up"; + } + } + } + } + } + } + + Logic { + id: logic + + onLauncherAdded: { + var m = isPopup ? popup.mainItem.popupModel : launcherModel; + m.appendUrl(url); + } + + onLauncherEdited: { + var m = isPopup ? popup.mainItem.popupModel : launcherModel; + m.changeUrl(index, url); + } + } + + // States to fix binding loop with enabled popup + states: [ + State { + name: "normal" + when: !vertical + + PropertyChanges { + target: popupArrow + width: Kirigami.Units.iconSizes.smallMedium + height: root.height + } + }, + + State { + name: "vertical" + when: vertical + + PropertyChanges { + target: popupArrow + width: root.width + height: Kirigami.Units.iconSizes.smallMedium + } + } + ] + + Connections { + target: plasmoid.configuration + function onLauncherUrlsChanged() { + launcherModel.urlsChanged.disconnect(saveConfiguration); + launcherModel.setUrls(plasmoid.configuration.launcherUrls); + launcherModel.urlsChanged.connect(saveConfiguration); + } + } + + Plasmoid.contextualActions: [ + PlasmaCore.Action { + text: i18nc("@action", "Add Launcher…") + icon.name: "list-add" + onTriggered: logic.addLauncher() + } + ] + + Component.onCompleted: { + launcherModel.setUrls(plasmoid.configuration.launcherUrls); + launcherModel.urlsChanged.connect(saveConfiguration); + } + + function saveConfiguration() + { + if (!dragging) { + plasmoid.configuration.launcherUrls = launcherModel.urls(); + } + } + + function isInternalDrop(event) + { + return event.mimeData.source + && event.mimeData.source.GridView + && event.mimeData.source.GridView.view == grid; + } +} diff --git a/applets/quicklaunch/package/metadata.json b/applets/quicklaunch/package/metadata.json new file mode 100644 index 00000000..78492f85 --- /dev/null +++ b/applets/quicklaunch/package/metadata.json @@ -0,0 +1,133 @@ +{ + "KPackageStructure": "Plasma/Applet", + "KPlugin": { + "Authors": [ + { + "Email": "nowrep@gmail.com", + "Name": "David Rosca", + "Name[ar]": "ديفيد روسكا", + "Name[az]": "David Rosca", + "Name[bg]": "David Rosca", + "Name[ca@valencia]": "David Rosca", + "Name[ca]": "David Rosca", + "Name[cs]": "David Rosca", + "Name[da]": "David Rosca", + "Name[de]": "David Rosca", + "Name[en_GB]": "David Rosca", + "Name[eo]": "David Rosca", + "Name[es]": "David Rosca", + "Name[eu]": "David Rosca", + "Name[fi]": "David Rosca", + "Name[fr]": "David Rosca", + "Name[gl]": "David Rosca", + "Name[he]": "דיויד רוסקה", + "Name[hu]": "David Rosca", + "Name[ia]": "David Rosca", + "Name[id]": "David Rosca", + "Name[is]": "David Rosca", + "Name[it]": "David Rosca", + "Name[ja]": "David Rosca", + "Name[ka]": "David Rosca", + "Name[ko]": "David Rosca", + "Name[lt]": "David Rosca", + "Name[lv]": "David Rosca", + "Name[nl]": "David Rosca", + "Name[nn]": "David Rosca", + "Name[pl]": "David Rosca", + "Name[pt]": "David Rosca", + "Name[pt_BR]": "David Rosca", + "Name[ro]": "David Rosca", + "Name[ru]": "David Rosca", + "Name[sk]": "David Rosca", + "Name[sl]": "David Rosca", + "Name[sv]": "David Rosca", + "Name[tr]": "David Rosca", + "Name[uk]": "David Rosca", + "Name[vi]": "David Rosca", + "Name[x-test]": "xxDavid Roscaxx", + "Name[zh_CN]": "David Rosca", + "Name[zh_TW]": "David Rosca" + } + ], + "BugReportUrl": "https://bugs.kde.org/enter_bug.cgi?product=kdeplasma-addons&component=Quicklaunch", + "Category": "Application Launchers", + "Description": "Launch your favourite applications", + "Description[ar]": "تشغل تطبيقاتك المفضلة", + "Description[bg]": "Стартиране на вашите предпочитани приложения", + "Description[ca@valencia]": "Inicia les vostres aplicacions preferides", + "Description[ca]": "Llança les vostres aplicacions preferides", + "Description[cs]": "Spouštějte své oblíbené aplikace", + "Description[de]": "Ermöglicht den Aufruf Ihrer Lieblingsprogramme", + "Description[en_GB]": "Launch your favourite applications", + "Description[es]": "Lanzar sus aplicaciones favoritas", + "Description[eu]": "Abiarazi zure aplikazio gogokoenak", + "Description[fr]": "Lancer vos applications préférées", + "Description[gl]": "Inicia as súas aplicacións favoritas", + "Description[he]": "שיגור היישומים המועדפים עליך", + "Description[hu]": "A kedvenc alkalmazásainak indítása", + "Description[ia]": "Lancea tu applicationes favorite", + "Description[it]": "Avvia le tue applicazioni preferite", + "Description[ka]": "გაუშვით თქვენი რჩეული პროგრამები", + "Description[nn]": "Start favoritt­programma dine", + "Description[pl]": "Uruchamia ulubione programy", + "Description[ru]": "Быстрый запуск избранных приложений", + "Description[sl]": "Zaženi vaše priljubljene aplikacije", + "Description[sv]": "Start dina favoritprogram", + "Description[tr]": "Favori uygulamaları çalıştır", + "Description[uk]": "Запуск ваших улюблених програм", + "Description[x-test]": "xxLaunch your favourite applicationsxx", + "Description[zh_CN]": "启动您最常用的程序", + "Description[zh_TW]": "啟動您最愛的應用程式", + "FormFactors": [ + "desktop" + ], + "Icon": "fork", + "Id": "org.kde.plasma.quicklaunch", + "License": "GPL-2.0+", + "Name": "Quicklaunch", + "Name[ar]": "الإطلاق السّريع", + "Name[az]": "Sürətli başlatmaq", + "Name[bg]": "Бързо стартиране", + "Name[ca@valencia]": "Quicklaunch", + "Name[ca]": "Quicklaunch", + "Name[cs]": "Rychlé spuštění", + "Name[da]": "Hurtigstart", + "Name[de]": "Schnellstarter", + "Name[en_GB]": "Quicklaunch", + "Name[eo]": "Rapida lanĉo", + "Name[es]": "Lanzamiento rápido", + "Name[eu]": "Quicklaunch", + "Name[fi]": "Pikakäynnistin", + "Name[fr]": "Lancement rapide", + "Name[gl]": "Quicklaunch", + "Name[he]": "משגר מהיר", + "Name[hu]": "Gyorsindító", + "Name[ia]": "Quicklaunch (lancea rapidemente)", + "Name[id]": "Quicklaunch", + "Name[is]": "Flýtiræsing", + "Name[it]": "Avvio rapido", + "Name[ja]": "クイック起動", + "Name[ka]": "სწრაფგამშვები", + "Name[ko]": "빠른 실행", + "Name[lt]": "Spartusis paleidimas", + "Name[lv]": "Ātrais palaidējs", + "Name[nl]": "Snelstarten", + "Name[nn]": "Snøgg­startar", + "Name[pl]": "Szybkie uruchamianie", + "Name[pt]": "Lançador Rápido", + "Name[pt_BR]": "Lançamento rápido", + "Name[ro]": "Lansare rapidă", + "Name[ru]": "Панель запуска", + "Name[sk]": "Rýchle spustenie", + "Name[sl]": "Quicklaunch", + "Name[sv]": "Snabbstart", + "Name[tr]": "Hizlı Başlat", + "Name[uk]": "Швидкий запуск", + "Name[vi]": "Khởi chạy nhanh", + "Name[x-test]": "xxQuicklaunchxx", + "Name[zh_CN]": "快速启动", + "Name[zh_TW]": "快速啟動", + "Website": "https://kde.org/plasma-desktop/" + }, + "X-Plasma-API-Minimum-Version": "6.0" +} diff --git a/applets/quicklaunch/plugin/CMakeLists.txt b/applets/quicklaunch/plugin/CMakeLists.txt new file mode 100644 index 00000000..2add5dd5 --- /dev/null +++ b/applets/quicklaunch/plugin/CMakeLists.txt @@ -0,0 +1,13 @@ +ecm_add_qml_module(quicklaunchplugin URI org.kde.plasma.private.quicklaunch) +target_sources(quicklaunchplugin PRIVATE + quicklaunch_p.cpp + quicklaunchplugin.cpp +) +target_link_libraries(quicklaunchplugin PRIVATE + Qt::Core + Qt::Qml + KF6::KIOCore + KF6::KIOWidgets + KF6::Notifications +) +ecm_finalize_qml_module(quicklaunchplugin) diff --git a/applets/quicklaunch/plugin/quicklaunch_p.cpp b/applets/quicklaunch/plugin/quicklaunch_p.cpp new file mode 100644 index 00000000..e71b8d64 --- /dev/null +++ b/applets/quicklaunch/plugin/quicklaunch_p.cpp @@ -0,0 +1,216 @@ +/* + * SPDX-FileCopyrightText: 2008-2009 Lukas Appelhans + * SPDX-FileCopyrightText: 2010-2011 Ingomar Wesp + * SPDX-FileCopyrightText: 2013 Bhushan Shah + * SPDX-FileCopyrightText: 2015 David Rosca + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + */ + +#include "quicklaunch_p.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +QuicklaunchPrivate::QuicklaunchPrivate(QObject *parent) + : QObject(parent) +{ +} + +QVariantMap QuicklaunchPrivate::launcherData(const QUrl &url) +{ + QString name; + QString icon; + QString genericName; + QVariantList jumpListActions; + + if (url.scheme() == QLatin1String("quicklaunch")) { + // Ignore internal scheme + } else if (url.isLocalFile()) { + const KFileItem fileItem(url); + const QFileInfo fi(url.toLocalFile()); + + if (fileItem.isDesktopFile()) { + const KDesktopFile f(url.toLocalFile()); + name = f.readName(); + icon = f.readIcon(); + genericName = f.readGenericName(); + if (name.isEmpty()) { + name = QFileInfo(url.toLocalFile()).fileName(); + } + + const QStringList &actions = f.readActions(); + + for (const QString &actionName : actions) { + const KConfigGroup &actionGroup = f.actionGroup(actionName); + + if (!actionGroup.isValid() || !actionGroup.exists()) { + continue; + } + + const QString &name = actionGroup.readEntry("Name"); + const QString &exec = actionGroup.readEntry("Exec"); + if (name.isEmpty() || exec.isEmpty()) { + continue; + } + + jumpListActions << QVariantMap{{QStringLiteral("name"), name}, + {QStringLiteral("icon"), actionGroup.readEntry("Icon")}, + {QStringLiteral("exec"), exec}}; + } + } else { + QMimeDatabase db; + name = fi.baseName(); + icon = db.mimeTypeForUrl(url).iconName(); + genericName = fi.baseName(); + } + } else { + if (url.scheme().contains(QLatin1String("http"))) { + name = url.host(); + } else if (name.isEmpty()) { + name = url.toString(); + if (name.endsWith(QLatin1String(":/"))) { + name = url.scheme(); + } + } + icon = KIO::iconNameForUrl(url); + } + + return QVariantMap{{QStringLiteral("applicationName"), name}, + {QStringLiteral("iconName"), icon}, + {QStringLiteral("genericName"), genericName}, + {QStringLiteral("jumpListActions"), jumpListActions}}; +} + +void QuicklaunchPrivate::openUrl(const QUrl &url) +{ + auto *job = new KIO::OpenUrlJob(url); + job->setUiDelegate(new KNotificationJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled)); + job->setRunExecutables(true); + job->start(); +} + +void QuicklaunchPrivate::openExec(const QString &exec) +{ + KIO::CommandLauncherJob *job = new KIO::CommandLauncherJob(exec); + job->setUiDelegate(new KNotificationJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled)); + job->start(); +} + +void QuicklaunchPrivate::addLauncher(bool isPopup) +{ + KOpenWithDialog *dialog = new KOpenWithDialog(); + dialog->setModal(false); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->hideRunInTerminal(); + dialog->setSaveNewApplications(true); + dialog->show(); + + connect(dialog, &KOpenWithDialog::accepted, this, [this, dialog, isPopup]() { + if (!dialog->service()) { + return; + } + const QUrl &url = QUrl::fromLocalFile(dialog->service()->entryPath()); + if (url.isValid()) { + Q_EMIT launcherAdded(url.toString(), isPopup); + } + }); +} + +static QString locateLocal(const QString &file) +{ + const QString &dataPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + const QString appDataPath = QStringLiteral("%1/quicklaunch").arg(dataPath); + QDir().mkpath(appDataPath); + return QStringLiteral("%1/%2").arg(appDataPath, file); +} + +static QString determineNewDesktopFilePath(const QString &baseName) +{ + QString appendix; + QString desktopFilePath = locateLocal(baseName) + QLatin1String(".desktop"); + + auto *generator = QRandomGenerator::global(); + while (QFile::exists(desktopFilePath)) { + if (appendix.isEmpty()) { + appendix += QLatin1Char('-'); + } + + // Limit to [0-9] and [a-z] range. + char newChar = generator->bounded(36); + newChar += newChar < 10 ? 48 : 97 - 10; + appendix += QLatin1Char(newChar); + + desktopFilePath = locateLocal(baseName + appendix + QLatin1String(".desktop")); + } + + return desktopFilePath; +} + +void QuicklaunchPrivate::editLauncher(QUrl url, int index, bool isPopup) +{ + // If the launcher does not point to a desktop file, create one, + // so that user can change url, icon, text and description. + bool desktopFileCreated = false; + + if (!url.isLocalFile() || !KDesktopFile::isDesktopFile(url.toLocalFile())) { + const QString desktopFilePath = determineNewDesktopFilePath(QStringLiteral("launcher")); + const QVariantMap data = launcherData(url); + + KConfig desktopFile(desktopFilePath); + KConfigGroup desktopEntry(&desktopFile, "Desktop Entry"); + + desktopEntry.writeEntry("Name", data.value(QStringLiteral("applicationName")).toString()); + desktopEntry.writeEntry("Comment", data.value(QStringLiteral("genericName")).toString()); + desktopEntry.writeEntry("Icon", data.value(QStringLiteral("iconName")).toString()); + desktopEntry.writeEntry("Type", "Link"); + desktopEntry.writeEntry("URL", url); + + desktopEntry.sync(); + + url = QUrl::fromLocalFile(desktopFilePath); + desktopFileCreated = true; + } + + KPropertiesDialog *dialog = new KPropertiesDialog(url); + dialog->setModal(false); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->show(); + + connect(dialog, &KPropertiesDialog::accepted, this, [this, dialog, index, isPopup]() { + QUrl url = dialog->url(); + QString path = url.toLocalFile(); + + // If the user has renamed the file, make sure that the new + // file name has the extension ".desktop". + if (!path.endsWith(QLatin1String(".desktop"))) { + QFile::rename(path, path + QLatin1String(".desktop")); + path += QLatin1String(".desktop"); + url = QUrl::fromLocalFile(path); + } + Q_EMIT launcherEdited(url.toString(), index, isPopup); + }); + + connect(dialog, &KPropertiesDialog::rejected, this, [url, desktopFileCreated]() { + if (desktopFileCreated) { + // User didn't save the data, delete the temporary desktop file. + QFile::remove(url.toLocalFile()); + } + }); +} diff --git a/applets/quicklaunch/plugin/quicklaunch_p.h b/applets/quicklaunch/plugin/quicklaunch_p.h new file mode 100644 index 00000000..20ec98bb --- /dev/null +++ b/applets/quicklaunch/plugin/quicklaunch_p.h @@ -0,0 +1,33 @@ +/* + * SPDX-FileCopyrightText: 2015 David Rosca + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + */ + +#ifndef QUICKLAUNCH_P_H +#define QUICKLAUNCH_P_H + +#include +#include +#include + +class QuicklaunchPrivate : public QObject +{ + Q_OBJECT + +public: + explicit QuicklaunchPrivate(QObject *parent = nullptr); + + Q_INVOKABLE QVariantMap launcherData(const QUrl &url); + Q_INVOKABLE void openUrl(const QUrl &url); + Q_INVOKABLE void openExec(const QString &exec); + + Q_INVOKABLE void addLauncher(bool isPopup = false); + Q_INVOKABLE void editLauncher(QUrl url, int index, bool isPopup = false); + +Q_SIGNALS: + void launcherAdded(const QString &url, bool isPopup); + void launcherEdited(const QString &url, int index, bool isPopup); +}; + +#endif // QUICKLAUNCH_P_H diff --git a/applets/quicklaunch/plugin/quicklaunchplugin.cpp b/applets/quicklaunch/plugin/quicklaunchplugin.cpp new file mode 100644 index 00000000..9aa02e84 --- /dev/null +++ b/applets/quicklaunch/plugin/quicklaunchplugin.cpp @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2015 David Rosca + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + */ + +#include "quicklaunch_p.h" + +#include +#include + +class QuicklaunchPlugin : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") + +public: + void registerTypes(const char *uri) override + { + qmlRegisterType(uri, 1, 0, "Logic"); + } +}; + +#include "quicklaunchplugin.moc" diff --git a/applets/timer/CMakeLists.txt b/applets/timer/CMakeLists.txt new file mode 100644 index 00000000..050cb69f --- /dev/null +++ b/applets/timer/CMakeLists.txt @@ -0,0 +1,9 @@ +plasma_install_package(package org.kde.plasma.timer) + +ecm_add_qml_module(timerplugin URI org.kde.plasma.private.timer) +target_sources(timerplugin PRIVATE plugin/timerplugin.cpp) +target_link_libraries(timerplugin PRIVATE Qt::Core Qt::Qml) +ecm_finalize_qml_module(timerplugin) + +install(FILES timer.svgz DESTINATION ${PLASMA_DATA_INSTALL_DIR}/desktoptheme/default/widgets/) +install(FILES plasma_applet_timer.notifyrc DESTINATION ${KDE_INSTALL_KNOTIFYRCDIR}) diff --git a/applets/timer/Messages.sh b/applets/timer/Messages.sh new file mode 100755 index 00000000..89fa9d30 --- /dev/null +++ b/applets/timer/Messages.sh @@ -0,0 +1,3 @@ +#! /usr/bin/env bash +$EXTRACTRC *.ui >> rc.cpp +$XGETTEXT `find . -name \*.qml -o -name \*.cpp` -o $podir/plasma_applet_org.kde.plasma.timer.pot diff --git a/applets/timer/package/contents/config/config.qml b/applets/timer/package/contents/config/config.qml new file mode 100644 index 00000000..6bdf4ae7 --- /dev/null +++ b/applets/timer/package/contents/config/config.qml @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: 2015 Bernhard Friedreich + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.2 + +import org.kde.plasma.configuration 2.0 + +ConfigModel { + ConfigCategory { + name: i18nc("@title", "Appearance") + icon: "preferences-desktop-color" + source: "configAppearance.qml" + } + + ConfigCategory { + name: i18nc("@title", "Predefined Timers") + icon: "chronometer" + source: "configTimes.qml" + } + ConfigCategory { + name: i18nc("@title", "Advanced") + icon: "preferences-other" + source: "configAdvanced.qml" + } +} diff --git a/applets/timer/package/contents/config/main.xml b/applets/timer/package/contents/config/main.xml new file mode 100644 index 00000000..a5de79b6 --- /dev/null +++ b/applets/timer/package/contents/config/main.xml @@ -0,0 +1,62 @@ + + + + + + + 30,60,120,300,450,600,900,1200,1500,1800,2700,3600 + + + + 0 + + + 0 + + + + + false + + + + Timer + + + + true + + + + true + + + + true + + + + false + + + + true + + + + Timer finished + + + + false + + + + + + + + diff --git a/applets/timer/package/contents/ui/CompactRepresentation.qml b/applets/timer/package/contents/ui/CompactRepresentation.qml new file mode 100644 index 00000000..e96faf43 --- /dev/null +++ b/applets/timer/package/contents/ui/CompactRepresentation.qml @@ -0,0 +1,248 @@ +/* + * SPDX-FileCopyrightText: 2022 Fushan Wen + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import QtQuick +import QtQuick.Layouts 1.15 + +import org.kde.kirigami 2.20 as Kirigami +import org.kde.plasma.components 3.0 as PlasmaComponents3 +import org.kde.plasma.plasmoid 2.0 + +import org.kde.plasma.private.timer 0.1 as TimerPlasmoid + +Item { + id: compactRepresentation + + Layout.minimumHeight: root.inPanel ? Layout.preferredHeight : -1 + + Layout.preferredWidth: grid.width + Layout.preferredHeight: grid.height + + enum LayoutType { + HorizontalPanel, + VerticalPanel, + HorizontalDesktop, + VerticalDesktop, + IconOnly + } + + property int layoutForm + + Binding on layoutForm { + delayed: true + value: { + if (root.inPanel) { + return root.isVertical ? CompactRepresentation.LayoutType.VerticalPanel : CompactRepresentation.LayoutType.HorizontalPanel; + } + if (compactRepresentation.parent.width - iconItem.Layout.preferredWidth >= remainingTimeLabel.contentWidth) { + return CompactRepresentation.LayoutType.HorizontalDesktop; + } + if (compactRepresentation.parent.height - iconItem.Layout.preferredHeight >= remainingTimeLabel.contentHeight) { + return CompactRepresentation.LayoutType.VerticalDesktop; + } + return CompactRepresentation.LayoutType.IconOnly; + } + } + + Keys.onUpPressed: adjustSecond(10); + Keys.onDownPressed: adjustSecond(-10); + + function adjustSecond(value) { + while (value >= 15 && root.seconds + 1 < 24*60*60) { + root.seconds += 1; + value -= 15; + } + while (value <= -15 && root.seconds - 1 >= 0) { + root.seconds -= 1; + value += 15; + } + return value; + } + + WheelHandler { + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + enabled: !root.running + onWheel: { + event.accepted = true; + rotation = compactRepresentation.adjustSecond(rotation); + } + } + + GridLayout { + id: grid + + width: { + switch (compactRepresentation.layoutForm) { + case CompactRepresentation.LayoutType.HorizontalPanel: + case CompactRepresentation.LayoutType.HorizontalDesktop: + return implicitWidth; + case CompactRepresentation.LayoutType.VerticalPanel: + case CompactRepresentation.LayoutType.VerticalDesktop: + return compactRepresentation.parent.width; + case CompactRepresentation.LayoutType.IconOnly: + return iconItem.Layout.preferredWidth; + } + } + height: { + switch (compactRepresentation.layoutForm) { + case CompactRepresentation.LayoutType.HorizontalPanel: + case CompactRepresentation.LayoutType.HorizontalDesktop: + case CompactRepresentation.LayoutType.VerticalDesktop: + return compactRepresentation.parent.height; + case CompactRepresentation.LayoutType.VerticalPanel: + return implicitHeight; + case CompactRepresentation.LayoutType.IconOnly: + return iconItem.Layout.preferredHeight; + } + } + + rowSpacing: 0 + columnSpacing: rowSpacing + flow: { + switch (compactRepresentation.layoutForm) { + case CompactRepresentation.LayoutType.VerticalPanel: + case CompactRepresentation.LayoutType.VerticalDesktop: + return GridLayout.TopToBottom; + default: + return GridLayout.LeftToRight; + } + } + + Item { + id: spacerItem + Layout.fillHeight: true + visible: layoutForm === CompactRepresentation.LayoutType.VerticalDesktop + } + + PlasmaComponents3.ToolButton { + id: iconItem + + Layout.alignment: Qt.AlignVCenter + Layout.preferredWidth: Math.min(compactRepresentation.parent.width, compactRepresentation.parent.height) + Layout.preferredHeight: Layout.preferredWidth + visible: root.showTimerToggle + + display: PlasmaComponents3.AbstractButton.IconOnly + icon.name: { + if (root.running) { + return "chronometer-pause"; + } + return root.seconds > 0 ? "chronometer-start" : "chronometer"; + } + text: root.running ? i18nc("@action:button", "Pause Timer") : i18nc("@action:button", "Start Timer") + + onClicked: { + if (root.seconds === 0) { + root.expanded = !root.expanded; + } else { + root.toggleTimer(); + } + } + } + + ColumnLayout { + Layout.alignment: Qt.AlignVCenter + Layout.fillWidth: layoutForm === CompactRepresentation.LayoutType.VerticalPanel || layoutForm === CompactRepresentation.LayoutType.VerticalDesktop + Layout.maximumWidth: { + switch (layoutForm) { + case CompactRepresentation.LayoutType.HorizontalPanel: + return Kirigami.Units.gridUnit * 10; + case CompactRepresentation.LayoutType.HorizontalDesktop: + return compactRepresentation.parent.width - iconItem.Layout.preferredWidth; + default: + return -1; + } + } + Layout.maximumHeight: textMetrics.height * 2 + visible: compactRepresentation.layoutForm !== CompactRepresentation.LayoutType.IconOnly ? 1 : 0 + + spacing: parent.columnSpacing + + TapHandler { + acceptedButtons: Qt.LeftButton + onTapped: root.expanded = !root.expanded + } + + PlasmaComponents3.Label { + id: titleLabel + + Layout.fillWidth: true + Layout.fillHeight: true + visible: root.showTitle && root.title !== "" + + elide: Text.ElideRight + font.bold: remainingTimeLabel.font.bold + fontSizeMode: remainingTimeLabel.fontSizeMode + horizontalAlignment: remainingTimeLabel.horizontalAlignment + minimumPointSize: remainingTimeLabel.minimumPointSize + text: root.title + textFormat: Text.PlainText + } + + PlasmaComponents3.Label { + id: remainingTimeLabel + + Layout.fillWidth: parent.Layout.fillWidth + Layout.fillHeight: true + Layout.maximumWidth: Layout.fillWidth ? -1 : textMetrics.width + Layout.minimumWidth: Layout.maximumWidth + visible: root.showRemainingTime + + TextMetrics { + id: textMetrics + text: { + if (root.isVertical) { + return i18ncp("remaining time", "%1s", "%1s", root.seconds); + } + // make it not jump around: reserve space for one extra digit than reasonable + return root.showSeconds ? "44:44:444" : "44:444"; + } + font: remainingTimeLabel.font + } + + activeFocusOnTab: true + elide: root.inPanel ? Text.ElideRight : Text.ElideNone + font.bold: root.alertMode + fontSizeMode: layoutForm === CompactRepresentation.LayoutType.HorizontalPanel || layoutForm === CompactRepresentation.LayoutType.HorizontalDesktop ? Text.VerticalFit : Text.HorizontalFit + horizontalAlignment: layoutForm === CompactRepresentation.LayoutType.HorizontalPanel || layoutForm === CompactRepresentation.LayoutType.HorizontalDesktop ? Text.AlignJustify : Text.AlignHCenter + minimumPointSize: Kirigami.Theme.smallFont.pointSize + verticalAlignment: Text.AlignVCenter + + text: { + if (root.isVertical) { + return i18ncp("remaining time", "%1s", "%1s", root.seconds); + } + + return root.showSeconds ? TimerPlasmoid.Timer.secondsToString(root.seconds, "hh:mm:ss") : TimerPlasmoid.Timer.secondsToString(root.seconds, "hh:mm"); + } + textFormat: Text.PlainText + + Accessible.name: Plasmoid.toolTipMainText + Accessible.description: Plasmoid.toolTipSubText + Accessible.role: Accessible.Button + } + + PlasmaComponents3.ProgressBar { + id: remainingTimeProgressBar + + Layout.fillWidth: true + Layout.fillHeight: true + Layout.maximumWidth: (parent.visibleChildren.length > 1) ? Math.max(titleLabel.width, remainingTimeLabel.width) : -1 + Layout.minimumWidth: Layout.maximumWidth + visible: root.showProgressBar + + from: plasmoid.configuration.seconds + to: 0 + value: root.seconds + } + } + + Item { + Layout.fillHeight: true + visible: spacerItem.visible + } + } +} diff --git a/applets/timer/package/contents/ui/TimerEdit.qml b/applets/timer/package/contents/ui/TimerEdit.qml new file mode 100644 index 00000000..f7c55646 --- /dev/null +++ b/applets/timer/package/contents/ui/TimerEdit.qml @@ -0,0 +1,177 @@ +/* + * SPDX-FileCopyrightText: 2020 Łukasz Korbel + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.15 +import org.kde.plasma.plasmoid 2.0 +import org.kde.plasma.core as PlasmaCore +import org.kde.ksvg 1.0 as KSvg +import org.kde.kquickcontrolsaddons 2.0 as QtExtra +import org.kde.plasma.private.timer 0.1 as TimerPlasmoid + +Row { + id: timerEdit + property int value // time in seconds + property bool editable: true + // in alert mode 2nd set of digits from svg file will be drawn + property bool alertMode: false + + signal digitModified(int valueDelta) + + QtObject { + id: internal + readonly property string digitSuffix: alertMode ? "_1" : "" + // digits count include separators with 50% of digit width + readonly property real digits: root.showSeconds ? 7 : 4.5; + readonly property int digitH: (parent.height / 2) * digits < parent.width ? parent.height : parent.width / digits * 2 + readonly property int digitW: digitH / 2; + property string valueString: "000000" + } + + Component { + id: digit + KSvg.SvgItem { + property int meaning + property int num + readonly property int mouseWheelAngleThreshold: 5 + width: internal.digitW + height: internal.digitH + + activeFocusOnTab: true + imagePath: "widgets/timer" + elementId: num + internal.digitSuffix + + Keys.onPressed: { + switch (event.key) { + case Qt.Key_Return: + case Qt.Key_Enter: + case Qt.Key_Space: + case Qt.Key_Select: + root.toggleTimer(); + break; + case Qt.Key_Up: + if (value + meaning < 24*60*60) { + timerEdit.digitModified(meaning) + } + break; + case Qt.Key_Down: + if (value - meaning >= 0) { + timerEdit.digitModified(-meaning) + } + break; + case Qt.Key_Left: + nextItemInFocusChain(false).forceActiveFocus(Qt.BacktabFocusReason); + break; + case Qt.Key_Right: + nextItemInFocusChain(true).forceActiveFocus(Qt.TabFocusReason); + break; + default: + return; + } + event.accepted = true; + } + + MouseArea { + anchors.fill: parent + enabled: editable + propagateComposedEvents: true + + onWheel: wheel => { + wheel.accepted = true + if (wheel.angleDelta.y > mouseWheelAngleThreshold) { + if (value + meaning < 24*60*60) { + timerEdit.digitModified(meaning) + } + } else if (wheel.angleDelta.y < -mouseWheelAngleThreshold) { + if (value - meaning >= 0) { + timerEdit.digitModified(-meaning) + } + } + } + } + } + } + + Component { + id: separator + KSvg.SvgItem { + width: internal.digitW / 2; + height: internal.digitH; + imagePath: "widgets/timer" + elementId: "separator" + internal.digitSuffix + } + } + + // Following 8 loaders will prepare all elements + // to display time in format "hh:mm:ss" + + Loader { + id: hour1 + sourceComponent: digit + onLoaded: { + item.meaning = 60*60*10 //10h + item.num = internal.valueString[0] + } + } + Loader { + id: hour2 + sourceComponent: digit + onLoaded: { + item.meaning = 60*60 //1h + item.num = internal.valueString[1] + } + } + + Loader { sourceComponent: separator } // ":" + + Loader { + id: minute1 + sourceComponent: digit + onLoaded: { + item.meaning = 600 //10min + item.num = internal.valueString[2] + } + } + Loader { + id: minute2 + sourceComponent: digit + onLoaded: { + item.meaning = 60 //1min + item.num = internal.valueString[3] + } + } + + Loader { sourceComponent: root.showSeconds ? separator : undefined } // ":" + + Loader { + id: second1 + sourceComponent: root.showSeconds ? digit : undefined + onLoaded: { + item.meaning = 10 //10s + item.num = internal.valueString[4] + } + } + Loader { + id: second2 + sourceComponent: root.showSeconds ? digit : undefined + onLoaded: { + item.meaning = 1 //1s + item.num = internal.valueString[5] + } + } + + onValueChanged: { + // update all 6 digits in one signal handler + internal.valueString = TimerPlasmoid.Timer.secondsToString(value, "hhmmss") + if (hour1.item === null) return + hour1.item.num = internal.valueString[0] + hour2.item.num = internal.valueString[1] + minute1.item.num = internal.valueString[2] + minute2.item.num = internal.valueString[3] + second1.item.num = internal.valueString[4] + second2.item.num = internal.valueString[5] + } +} + diff --git a/applets/timer/package/contents/ui/TimerView.qml b/applets/timer/package/contents/ui/TimerView.qml new file mode 100644 index 00000000..2beee14d --- /dev/null +++ b/applets/timer/package/contents/ui/TimerView.qml @@ -0,0 +1,119 @@ +/* + * SPDX-FileCopyrightText: 2008, 2014 Davide Bettio + * SPDX-FileCopyrightText: 2015 Bernhard Friedreich + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.15 +import QtQuick.Layouts 1.15 + +import org.kde.plasma.plasmoid 2.0 +import org.kde.kirigami 2.20 as Kirigami +import org.kde.plasma.components 3.0 as PlasmaComponents3 +import org.kde.plasma.extras 2.0 as PlasmaExtras +import org.kde.kquickcontrolsaddons 2.0 as QtExtra + +MouseArea { + Layout.preferredWidth: Math.max(Plasmoid.compactRepresentationItem.width, Kirigami.Units.gridUnit * 10) + Layout.preferredHeight: main.implicitHeight + + onClicked: root.toggleTimer() + + Component { + id: popupHeadingComponent + + PlasmaExtras.PlasmoidHeading { + leftPadding: Kirigami.Units.smallSpacing * 2 + rightPadding: Kirigami.Units.smallSpacing * 2 + + contentItem: Kirigami.Heading { + level: 3 + elide: Text.ElideRight + horizontalAlignment: Text.AlignHCenter + text: root.title + textFormat: Text.PlainText + } + } + } + + Component { + id: desktopHeadingComponent + + PlasmaComponents3.Label { + elide: Text.ElideRight + font.pixelSize: 0.3 * timerDigits.height + text: root.title + textFormat: Text.PlainText + } + } + + ColumnLayout { + id: main + + width: parent.width + + Loader { + Layout.fillWidth: true + + active: root.showTitle + + sourceComponent: root.inPanel ? popupHeadingComponent : desktopHeadingComponent + } + + TimerEdit { + id: timerDigits + + Layout.fillWidth: true + visible: root.showRemainingTime + + value: root.seconds + editable: !root.running + alertMode: root.alertMode + onDigitModified: valueDelta => root.seconds += valueDelta + SequentialAnimation on opacity { + running: root.suspended; + loops: Animation.Infinite; + NumberAnimation { + duration: Kirigami.Units.veryLongDuration * 2; + from: 1.0; + to: 0.2; + easing.type: Easing.InOutQuad; + } + PauseAnimation { + duration: Kirigami.Units.veryLongDuration; + } + NumberAnimation { + duration: Kirigami.Units.veryLongDuration * 2; + from: 0.2; + to: 1.0; + easing.type: Easing.InOutQuad; + } + PauseAnimation { + duration: Kirigami.Units.veryLongDuration; + } + } + } + + PlasmaComponents3.ProgressBar { + id: remainingTimeProgressBar + + Layout.fillWidth: true + Layout.fillHeight: true + visible: root.showProgressBar + + from: plasmoid.configuration.seconds + to: 0 + value: root.seconds + } + + function resetOpacity() { + timerDigits.opacity = 1.0; + } + + Component.onCompleted: { + root.opacityNeedsReset.connect(resetOpacity); + } + } +} + diff --git a/applets/timer/package/contents/ui/configAdvanced.qml b/applets/timer/package/contents/ui/configAdvanced.qml new file mode 100644 index 00000000..164f69f1 --- /dev/null +++ b/applets/timer/package/contents/ui/configAdvanced.qml @@ -0,0 +1,41 @@ +/* + * SPDX-FileCopyrightText: 2015 Bernhard Friedrich + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.5 +import QtQuick.Controls 2.5 as QQC2 +import QtQuick.Layouts 1.0 + +import org.kde.kirigami 2.5 as Kirigami +import org.kde.kcmutils as KCM + +KCM.SimpleKCM { + property alias cfg_runCommand: runCommand.checked + property alias cfg_command: command.text + + Kirigami.FormLayout { + RowLayout { + Layout.fillWidth: true + + Kirigami.FormData.label: i18nc("@title:label", "After timer completes:") + + QQC2.CheckBox { + id: runCommand + text: i18nc("@option:check", "Execute command:") + onClicked: { + if (checked) { + command.forceActiveFocus(); + } + } + } + + QQC2.TextField { + id: command + Layout.fillWidth: true + enabled: runCommand.checked + } + } + } +} diff --git a/applets/timer/package/contents/ui/configAppearance.qml b/applets/timer/package/contents/ui/configAppearance.qml new file mode 100644 index 00000000..b39f9df0 --- /dev/null +++ b/applets/timer/package/contents/ui/configAppearance.qml @@ -0,0 +1,98 @@ + /* + * SPDX-FileCopyrightText: 2015 Bernhard Friedreich + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.5 +import QtQuick.Controls 2.5 as QQC2 +import QtQuick.Layouts 1.0 + +import org.kde.kirigami 2.5 as Kirigami +import org.kde.kcmutils as KCM + +KCM.SimpleKCM { + property alias cfg_showTitle: showTitle.checked + property alias cfg_title: title.text + + property alias cfg_showRemainingTime: showRemainingTime.checked + property alias cfg_showSeconds: showSeconds.checked + property alias cfg_showTimerToggle: showTimerToggle.checked + property alias cfg_showProgressBar: showProgressBar.checked + + property alias cfg_showNotification: showNotification.checked + property alias cfg_notificationText: notificationText.text + + Kirigami.FormLayout { + RowLayout { + Layout.fillWidth: true + + Kirigami.FormData.label: i18nc("@title:label", "Display:") + + QQC2.CheckBox { + id: showTitle + + text: i18nc("@option:check", "Show title:"); + onClicked: { + if (checked) { + title.forceActiveFocus(); + } + } + } + + QQC2.TextField { + id: title + Layout.fillWidth: true + enabled: showTitle.checked + } + } + + QQC2.CheckBox { + id: showRemainingTime + text: i18nc("@option:check", "Show remaining time"); + } + + QQC2.CheckBox { + id: showSeconds + enabled: showRemainingTime.checked + text: i18nc("@option:check", "Show seconds"); + } + + QQC2.CheckBox { + id: showTimerToggle + text: i18nc("@option:check", "Show timer toggle"); + } + + QQC2.CheckBox { + id: showProgressBar + text: i18nc("@option:check", "Show progress bar"); + } + + Item { + Kirigami.FormData.isSection: true + } + + RowLayout { + Layout.fillWidth: true + + Kirigami.FormData.label: i18nc("@title:label", "Notifications:") + + QQC2.CheckBox { + id: showNotification + text: i18nc("@option:check", "Show notification text:"); + onClicked: { + if (checked) { + notificationText.forceActiveFocus(); + } + } + } + + QQC2.TextField { + id: notificationText + Layout.fillWidth: true + enabled: showNotification.checked + } + } + } +} + diff --git a/applets/timer/package/contents/ui/configTimes.qml b/applets/timer/package/contents/ui/configTimes.qml new file mode 100644 index 00000000..a8b6ea1b --- /dev/null +++ b/applets/timer/package/contents/ui/configTimes.qml @@ -0,0 +1,163 @@ +/* + * SPDX-FileCopyrightText: 2020 Łukasz Korbel + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick +import QtQuick.Controls 2.5 as QQC2 +import QtQuick.Layouts 1.0 +import org.kde.kirigami 2.20 as Kirigami +import org.kde.plasma.plasmoid 2.0 +import org.kde.kcmutils as KCM + +KCM.ScrollViewKCM { + id: timesPage + property var cfg_predefinedTimers: [] + readonly property int maxListSize: 15 + + signal configurationChanged() + + Component.onCompleted: { + for (var i of plasmoid.configuration.predefinedTimers) { + timeListModel.append({"time": i}) + } + } + + ListModel { + id: timeListModel + function addTimer(value) { + timeListModel.append({"time": value}) + cfg_predefinedTimers.splice(count, 0, value) + timesPage.configurationChanged() + } + function removeTimer(index) { + remove(index) + cfg_predefinedTimers.splice(index, 1) + timesPage.configurationChanged() + } + function moveTimer(oldIndex, newIndex) { + move(oldIndex, newIndex, 1) + cfg_predefinedTimers[oldIndex] = get(oldIndex).time + cfg_predefinedTimers[newIndex] = get(newIndex).time + timesPage.configurationChanged() + } + function setTimer(index, newValue) { + setProperty(index, "time", newValue) + cfg_predefinedTimers[index] = newValue + timesPage.configurationChanged() + } + } + + view: ListView { + id: timeListView + anchors.margins: 4 + model: timeListModel + spacing: parent.spacing + clip: true + reuseItems: true + delegate: timeEditDelegate + add: Transition { + NumberAnimation { properties: "y"; duration: Kirigami.Units.longDuration } + } + displaced: Transition { + NumberAnimation { properties: "y"; duration: Kirigami.Units.longDuration } + } + + Kirigami.PlaceholderMessage { + visible: timeListView.count === 0 + anchors.centerIn: parent + width: parent.width - (Kirigami.Units.gridUnit * 4) + text: i18n("If you add predefined timers here, they will appear in plasmoid context menu."); + } + } + + footer: RowLayout { + QQC2.Button { + icon.name: "list-add" + text: i18n("Add") + enabled: timeListModel.count < maxListSize + onClicked: { + timeListModel.addTimer("0") + timeListView.positionViewAtEnd() + } + } + } + + Component { + id: timeEditDelegate + Kirigami.SwipeListItem { + id: timeEditItem + width: timeListView.width + contentItem: Row { + spacing: Kirigami.Units.gridUnit + Kirigami.ListItemDragHandle { + anchors.verticalCenter: parent.verticalCenter + enabled: !editor.editable + listItem: timeEditItem + listView: timeListView + onMoveRequested: (oldIndex, newIndex) => { + timeListModel.moveTimer(oldIndex, newIndex) + } + } + TimerEdit { + id: editor + alertMode: editable + value: time + property int oldValue: 0 + onDigitModified: valueDelta => { + set(value + valueDelta) + } + function set(newValue) { + timeListModel.setTimer(index, (newValue).toString()) + } + Component.onCompleted: editable = (value === 0) + } + QQC2.Label { + id: hintText + visible: editor.editable + text: i18n("Scroll over digits to change time") + textFormat: Text.PlainText + anchors.verticalCenter: parent.verticalCenter + } + } + actions: [ + Kirigami.Action { + text: i18n("Apply") + icon.name: "dialog-ok-apply" + visible: editor.editable + onTriggered: { + editor.editable = false + } + }, + Kirigami.Action { + text: i18n("Cancel") + icon.name: "dialog-cancel" + visible: editor.editable + onTriggered: { + editor.editable = false + editor.set(editor.oldValue) + } + }, + Kirigami.Action { + text: i18n("Edit") + icon.name: "edit-entry" + visible: editor.editable === false + onTriggered: { + editor.oldValue = editor.value + editor.editable = true + } + }, + Kirigami.Action { + text: i18n("Delete") + icon.name: "entry-delete" + visible: editor.editable === false + onTriggered: { + timeListModel.removeTimer(index) + } + } + ] + } + } +} + diff --git a/applets/timer/package/contents/ui/main.qml b/applets/timer/package/contents/ui/main.qml new file mode 100644 index 00000000..168a802e --- /dev/null +++ b/applets/timer/package/contents/ui/main.qml @@ -0,0 +1,213 @@ +/* + * SPDX-FileCopyrightText: 2016 Michael Abrahams + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import QtQuick 2.15 +import org.kde.plasma.plasmoid 2.0 +import org.kde.plasma.core as PlasmaCore +import org.kde.kirigami 2.20 as Kirigami +import org.kde.plasma.plasma5support 2.0 as P5Support +import org.kde.kquickcontrolsaddons 2.0 as QtExtra +import org.kde.plasma.private.timer 0.1 as TimerPlasmoid +import org.kde.notification 1.0 + +PlasmoidItem { + id: root; + + switchWidth: Kirigami.Units.gridUnit * 8 + switchHeight: Kirigami.Units.gridUnit * 4 + + readonly property bool inPanel: [PlasmaCore.Types.TopEdge, PlasmaCore.Types.RightEdge, PlasmaCore.Types.BottomEdge, PlasmaCore.Types.LeftEdge] + .includes(Plasmoid.location) + readonly property bool isVertical: Plasmoid.formFactor === PlasmaCore.Types.Vertical + + readonly property variant predefinedTimers: plasmoid.configuration.predefinedTimers; + + Plasmoid.backgroundHints: PlasmaCore.Types.ShadowBackground | PlasmaCore.Types.ConfigurableBackground + + // Display remaining time (hours and minutes) (default: enabled) + readonly property bool showRemainingTime: Plasmoid.configuration.showRemainingTime + + // Display seconds in addition to hours and minutes (default: enabled) + readonly property bool showSeconds: Plasmoid.configuration.showSeconds + property int seconds : restoreToSeconds(plasmoid.configuration.running, plasmoid.configuration.savedAt, plasmoid.configuration.seconds); + + // Display timer toggle control (default: enabled) + readonly property bool showTimerToggle: Plasmoid.configuration.showTimerToggle + + // Display progress bar (default: disabled) + readonly property bool showProgressBar: Plasmoid.configuration.showProgressBar + + // show notification on timer completion (default: enabled) + property bool showNotification: plasmoid.configuration.showNotification; + // run custom command on timer completion (default: disabled) + property bool runCommand: plasmoid.configuration.runCommand; + property string command: plasmoid.configuration.command; + + // show title (can be customized in the settings dialog, default: disabled) + readonly property bool showTitle: plasmoid.configuration.showTitle; + readonly property string title: plasmoid.configuration.title; + readonly property bool alertMode: root.running && root.seconds < 60 + property bool running: plasmoid.configuration.running > 0 + property bool suspended: false; + + readonly property string notificationText: plasmoid.configuration.notificationText; + + toolTipMainText: { + var timerName = ""; + if (showTitle && title != "") { + timerName = title; + } else { + timerName = Plasmoid.title; + } + + if (running) { + return i18n("%1 is running", timerName); + } else { + return i18n("%1 not running", timerName); + } + } + toolTipSubText: running ? i18np("Remaining time left: %1 second", "Remaining time left: %1 seconds", seconds) : i18n("Use mouse wheel to change digits or choose from predefined timers in the context menu"); + + compactRepresentation: CompactRepresentation { } + fullRepresentation: TimerView { } + + function toggleTimer() { + if (root.running) { + root.stopTimer(); + } else { + root.startTimer(); + } + } + + Notification { + id: timerNotification + componentName: "plasma_applet_timer" + eventId: "timerFinished" + title: root.title || i18n("Timer") + text: notificationText || i18n("Timer finished") + } + + Timer { + id: t; + interval: 1000; + onTriggered: { + if (root.seconds != 0) { + root.seconds--; + } + if (root.seconds == 0) { + root.running = false; + + if (showNotification) { + timerNotification.sendEvent(); + } + if (runCommand) { + TimerPlasmoid.Timer.runCommand(command); + } + saveTimer(); + } + } + repeat: true; + running: root.running; + } + + Timer { + id: delayedSaveTimer; + interval: 3000; + onTriggered: saveTimer(); + } + + function onDigitHasChanged() { + delayedSaveTimer.stop(); + delayedSaveTimer.start(); + } + + Plasmoid.contextualActions: [ + PlasmaCore.Action { + id: startAction + text: i18nc("@action", "&Start") + onTriggered: startTimer() + }, + PlasmaCore.Action { + id: stopAction + text: i18nc("@action", "S&top") + onTriggered: stopTimer() + }, + PlasmaCore.Action { + id: resetAction + text: i18nc("@action", "&Reset") + onTriggered: resetTimer() + }, + PlasmaCore.Action { + id: separator1 + isSeparator: true + }, + PlasmaCore.Action { + id: separator2 + isSeparator: true + } + ] + + Instantiator { + model: plasmoid.configuration.predefinedTimers + delegate: PlasmaCore.Action { + text: TimerPlasmoid.Timer.secondsToString(modelData, "hh:mm:ss") + onTriggered: { + seconds = modelData + startTimer(); + } + } + onObjectAdded: (index, object) => { + Plasmoid.contextualActions.splice(Plasmoid.contextualActions.indexOf(separator2), 0, object) + } + onObjectRemoved: (index, object) => { + Plasmoid.contextualActions.splice(Plasmoid.contextualActions.indexOf(object), 1) + } + } + + function startTimer() { + running = true; + suspended = false; + opacityNeedsReset(); + saveTimer(); + } + + function stopTimer() { + running = false; + suspended = true; + saveTimer(); + } + + function resetTimer() { + running = false; + suspended = false; + seconds = 0; + opacityNeedsReset(); + saveTimer(); + } + + signal opacityNeedsReset() + signal digitHasChanged() + + function saveTimer() { + plasmoid.configuration.running = running ? seconds : 0; + plasmoid.configuration.savedAt = new Date(); + plasmoid.configuration.seconds = seconds + } + + function restoreToSeconds(cRunning, cSavedAt, cSeconds) { + if (cRunning > 0) { + var elapsedSeconds = cRunning - ~~(~~(((new Date()).getTime() - cSavedAt.getTime()) / 1000)); + if (elapsedSeconds >= 0) { + return elapsedSeconds; + } else { + return 0; + } + } else { + return cSeconds; + } + } +} + diff --git a/applets/timer/package/metadata.json b/applets/timer/package/metadata.json new file mode 100644 index 00000000..a214c414 --- /dev/null +++ b/applets/timer/package/metadata.json @@ -0,0 +1,146 @@ +{ + "KPackageStructure": "Plasma/Applet", + "KPlugin": { + "Authors": [ + { + "Email": "davide.bettio@kdemail.net", + "Name": "Davide Bettio", + "Name[ar]": "ديفيد بيتيو", + "Name[az]": "Davide Bettio", + "Name[bg]": "Davide Bettio", + "Name[ca@valencia]": "Davide Bettio", + "Name[ca]": "Davide Bettio", + "Name[cs]": "Davide Bettio", + "Name[da]": "Davide Bettio", + "Name[de]": "Davide Bettio", + "Name[en_GB]": "Davide Bettio", + "Name[eo]": "Davide Bettio", + "Name[es]": "Davide Bettio", + "Name[eu]": "Davide Bettio", + "Name[fi]": "Davide Bettio", + "Name[fr]": "Davide Bettio", + "Name[gl]": "Davide Bettio", + "Name[he]": "דוד בטיו", + "Name[hu]": "Davide Bettio", + "Name[ia]": "Davide Bettio", + "Name[id]": "Davide Bettio", + "Name[is]": "Davide Bettio", + "Name[it]": "Davide Bettio", + "Name[ja]": "Davide Bettio", + "Name[ka]": "Davide Bettio", + "Name[ko]": "Davide Bettio", + "Name[lt]": "Davide Bettio", + "Name[lv]": "Davide Bettio", + "Name[nl]": "Davide Bettio", + "Name[nn]": "Davide Bettio", + "Name[pl]": "Davide Bettio", + "Name[pt]": "Davide Bettio", + "Name[pt_BR]": "Davide Bettio", + "Name[ro]": "Davide Bettio", + "Name[ru]": "Davide Bettio", + "Name[sk]": "Davide Bettio", + "Name[sl]": "Davide Bettio", + "Name[sv]": "Davide Bettio", + "Name[tr]": "Davide Bettio", + "Name[uk]": "Davide Bettio", + "Name[vi]": "Davide Bettio", + "Name[x-test]": "xxDavide Bettioxx", + "Name[zh_CN]": "Davide Bettio", + "Name[zh_TW]": "Davide Bettio" + } + ], + "BugReportUrl": "https://bugs.kde.org/enter_bug.cgi?product=kdeplasma-addons&component=timer", + "Category": "Date and Time", + "Description": "Countdown over a specified time period", + "Description[ar]": "قم بالعد التنازلي خلال فترة زمنية محددة", + "Description[az]": "Müəyyən bir vaxtın geri sayımı", + "Description[bg]": "Обратно броене за определено време", + "Description[ca@valencia]": "Compte arrere durant un període de temps especificat", + "Description[ca]": "Compte enrere durant un període de temps especificat", + "Description[cs]": "Odpočítávání událostí", + "Description[da]": "Nedtæller over en specificeret tidsperiode", + "Description[de]": "Einen bestimmten Zeitabschnitt zurückzählen", + "Description[en_GB]": "Countdown over a specified time period", + "Description[eo]": "Retronombrado dum difinita tempoperiodo", + "Description[es]": "Cuenta atrás del tiempo indicado", + "Description[eu]": "Atzekoz aurrera zenbatu zehaztutako denbora tarte batez", + "Description[fi]": "Laskenta annetulle ajalle", + "Description[fr]": "Lancer un décompte avec une période de temps spécifiée", + "Description[gl]": "Conta atrás durante un tempo especificado", + "Description[he]": "ספירה לאחור לפרק זמן מסוים", + "Description[hu]": "Visszaszámlálás egy meghatározott időtartam alatt", + "Description[ia]": "Computo a retro per unspecificate periodo de tempore", + "Description[id]": "Hitung mundur selama jangka waktu yang ditentukan", + "Description[is]": "Telja niður tíma fyrir tilgreint tímabil", + "Description[it]": "Conto alla rovescia a partire da un momento specificato", + "Description[ja]": "指定された時間をカウントダウンします", + "Description[ka]": "უკუათვლა მითითებული დროის პერიოდისთვის", + "Description[ko]": "지정한 시간 카운트다운하기", + "Description[lt]": "Atvirkštinis skaičiavimas per nurodytą laiko intervalą", + "Description[lv]": "Noteikta laika atskaitīšana", + "Description[nl]": "Aftellen over een bepaalde periode", + "Description[nn]": "Nedteljing til spesielle tidspunkt", + "Description[pl]": "Odlicza dany czas", + "Description[pt]": "Contagem decrescente para um dado período de tempo", + "Description[pt_BR]": "Contagem regressiva de um período de tempo indicado", + "Description[ro]": "Contorizează timpul descrescător pentru o anumită perioadă", + "Description[ru]": "Обратный отчёт на выбранный период времени", + "Description[sk]": "Odpočítavanie počas určitého časového obdobia", + "Description[sl]": "Odštevaj določeni časovni interval", + "Description[sv]": "Nerräkning under ett angivet tidsintervall", + "Description[tr]": "Belirli bir zamandan geriye say", + "Description[uk]": "Зворотній відлік визначеного часу", + "Description[vi]": "Đếm ngược một khoảng thời gian chỉ định", + "Description[x-test]": "xxCountdown over a specified time periodxx", + "Description[zh_CN]": "指定时间长度进行倒计时", + "Description[zh_TW]": "指定期間的倒數計時", + "Icon": "chronometer", + "Id": "org.kde.plasma.timer", + "License": "GPL-2.0+", + "Name": "Timer", + "Name[ar]": "مؤقت", + "Name[az]": "Vaxtölçən", + "Name[bg]": "Таймер", + "Name[ca@valencia]": "Temporitzador", + "Name[ca]": "Temporitzador", + "Name[cs]": "Časovač", + "Name[da]": "Timer", + "Name[de]": "Zeitgeber", + "Name[en_GB]": "Timer", + "Name[eo]": "Temporizilo", + "Name[es]": "Temporizador", + "Name[eu]": "Tenporizadorea", + "Name[fi]": "Ajastin", + "Name[fr]": "Minuteur", + "Name[gl]": "Temporizador", + "Name[he]": "קוצב זמן", + "Name[hu]": "Időzítő", + "Name[ia]": "Temporisator", + "Name[id]": "Timer", + "Name[is]": "Niðurtalning", + "Name[it]": "Timer", + "Name[ja]": "タイマー", + "Name[ka]": "წამმზომი", + "Name[ko]": "타이머", + "Name[lt]": "Laikmatis", + "Name[lv]": "Taimeris", + "Name[nl]": "Timer", + "Name[nn]": "Tidsur", + "Name[pl]": "Czasomierz", + "Name[pt]": "Cronómetro", + "Name[pt_BR]": "Temporizador", + "Name[ro]": "Temporizator", + "Name[ru]": "Таймер", + "Name[sk]": "Časovač", + "Name[sl]": "Timer", + "Name[sv]": "Tidtagare", + "Name[tr]": "Sayaç", + "Name[uk]": "Таймер", + "Name[vi]": "Bộ hẹn giờ", + "Name[x-test]": "xxTimerxx", + "Name[zh_CN]": "计时器", + "Name[zh_TW]": "計時器", + "Website": "https://kde.org/plasma-desktop/" + }, + "X-Plasma-API-Minimum-Version": "6.0" +} diff --git a/applets/timer/plasma_applet_timer.notifyrc b/applets/timer/plasma_applet_timer.notifyrc new file mode 100644 index 00000000..22384098 --- /dev/null +++ b/applets/timer/plasma_applet_timer.notifyrc @@ -0,0 +1,157 @@ +[Global] +Name=Timer +Name[ar]=مؤقت +Name[ast]=Temporizador +Name[az]=Vaxtölçən +Name[bg]=Таймер +Name[bs]=Timer +Name[ca]=Temporitzador +Name[ca@valencia]=Temporitzador +Name[cs]=Časovač +Name[da]=Nedtællingsur +Name[de]=Eieruhr +Name[el]=Χρονόμετρο +Name[en_GB]=Timer +Name[eo]=Tempigilo +Name[es]=Cronómetro +Name[et]=Taimer +Name[eu]=Tenporizadorea +Name[fi]=Ajastin +Name[fr]=Décompte +Name[ga]=Uaineadóir +Name[gl]=Temporizador +Name[he]=קוצב זמן +Name[hr]=Timer +Name[hu]=Időzítő indítása +Name[ia]=Temporisator +Name[id]=Timer +Name[is]=Niðurtalning +Name[it]=Temporizzatore +Name[ja]=タイマー +Name[ka]=წამმზომი +Name[kk]=Таймер +Name[km]=កម្មវិធី​កំណត់​ពេលវេលា +Name[ko]=타이머 +Name[ku]=Timer +Name[lt]=Laikmatis +Name[lv]=Taimeris +Name[mr]=टाइमर +Name[nb]=Tidsur +Name[nds]=Weckklock +Name[nl]=Timer +Name[nn]=Tidsur +Name[pa]=ਟਾਈਮਰ +Name[pl]=Czasomierz +Name[pt_BR]=Temporizador +Name[ro]=Temporizator +Name[ru]=Таймер +Name[sk]=Časovač +Name[sl]=Časomer +Name[sq]=Koha +Name[sr]=одбројавач +Name[sr@ijekavian]=одбројавач +Name[sr@ijekavianlatin]=odbrojavač +Name[sr@latin]=odbrojavač +Name[sv]=Tidtagning +Name[ta]=டைமர் +Name[tg]=Вақтсанҷ +Name[th]=เครื่องมือจับเวลา +Name[tr]=Sayaç +Name[ug]=ۋاقىت ئۆلچىگۈچ +Name[uk]=Таймер +Name[vi]=Hẹn giờ +Name[wa]=Munutreye +Name[x-test]=xxTimerxx +Name[zh_CN]=计时器 +Name[zh_TW]=計時器 +IconName=chronometer + +[Event/timerFinished] +Name=Timer finished +Name[ar]=انتهى المؤقّت +Name[az]=Sayma tamamlandı +Name[bg]=Таймерът завърши +Name[ca]=El temporitzador ha finalitzat +Name[ca@valencia]=El temporitzador ha finalitzat +Name[cs]=Časovač skončil +Name[da]=Timer færdig +Name[de]=Zeit abgelaufen +Name[el]=Ο χρόνος τελείωσε +Name[en_GB]=Timer finished +Name[eo]=Tempigilo finis +Name[es]=Temporizador terminado +Name[eu]=Tenporizadoreak amaitu du +Name[fi]=Ajastus päättynyt +Name[fr]=Décompte terminé +Name[gl]=O temporizador rematou +Name[he]=קוצב הזמן הסתיים +Name[hu]=Időzítő kész +Name[ia]=Chronometro terminava +Name[is]=Niðurtalningu lokið +Name[it]=Temporizzatore terminato +Name[ka]=წამმზომი დასრულდა +Name[ko]=타이머 완료됨 +Name[lt]=Pasibaigė laikmačio laikas +Name[lv]=Taimeris iztecējis +Name[nl]=Timer is geëindigd +Name[nn]=Nedteljinga er ferdig +Name[pa]=ਟਾਈਮਰ ਪੂਰਾ ਹੋਇਆ +Name[pl]=Czasomierz ukończył +Name[pt_BR]=O temporizador terminou +Name[ro]=Temporizatorul a expirat +Name[ru]=Отсчёт завершён +Name[sk]=Časovač skončil +Name[sl]=Časomer končal +Name[sv]=Tidtagning klar +Name[ta]=டைமர் முடிந்தவிட்டது +Name[tr]=Sayaç bitti +Name[uk]=Відлік завершено +Name[vi]=Bộ hẹn giờ kết thúc +Name[x-test]=xxTimer finishedxx +Name[zh_CN]=计时结束 +Name[zh_TW]=計時器已完成 +Comment=Your timer has finished +Comment[ar]=انتهى المؤقّت +Comment[az]=Sayma tamamlandı +Comment[bg]=Таймерът завърши +Comment[ca]=El temporitzador ha finalitzat +Comment[ca@valencia]=El temporitzador ha finalitzat +Comment[cs]=Váš časovač skončil +Comment[da]=Din timer er færdig +Comment[de]=Ihre Eieruhr ist abgelaufen +Comment[el]=Ο χρόνος σας τελείωσε +Comment[en_GB]=Your timer has finished +Comment[eo]=Via tempigilo finis +Comment[es]=El temporizador ha terminado +Comment[eu]=Zure denbora amaitu da +Comment[fi]=Ajastus on päättynyt +Comment[fr]=Votre décompte est terminé. +Comment[gl]=O temporizador rematou. +Comment[he]=קוצב הזמן שלך הסתיים +Comment[hu]=Az időzítője lejárt +Comment[ia]=Tu Chronometro terminava +Comment[is]=Niðurtalningunni er lokið +Comment[it]=Il tuo temporizzatore è terminato +Comment[ka]=თქვენი წამმზომი დასრულდა +Comment[ko]=타이머가 완료되었습니다 +Comment[lt]=Pasibaigė jūsų nustatyto laikmačio laikas +Comment[lv]=Taimera laiks ir beidzies +Comment[nl]=Uw timer is geëindigd +Comment[nn]=Nedteljinga er ferdig +Comment[pa]=ਤੁਹਾਡਾ ਟਾਈਮਰ ਪੂਰਾ ਹੋਇਆ +Comment[pl]=Twój czasomierz ukończył +Comment[pt_BR]=Seu temporizador terminou +Comment[ro]=Temporizatorul dumneavoastră a expirat +Comment[ru]=Отсчёт таймера завершён +Comment[sk]=Váš časovač sa skončil +Comment[sl]=Vaš časomer je končal +Comment[sv]=Tidtagningen är klar +Comment[ta]=உங்கள் டைமர் முடிந்தவிட்டது +Comment[tr]=Sayacınız bitti +Comment[uk]=Відлік за вашим таймером завершено +Comment[vi]=Bộ hẹn giờ đã kết thúc +Comment[x-test]=xxYour timer has finishedxx +Comment[zh_CN]=计时器已完成计时 +Comment[zh_TW]=您的計時器已完成 +Action=Sound|Popup +Sound=alarm-clock-elapsed diff --git a/applets/timer/plugin/timerplugin.cpp b/applets/timer/plugin/timerplugin.cpp new file mode 100644 index 00000000..7b2658a8 --- /dev/null +++ b/applets/timer/plugin/timerplugin.cpp @@ -0,0 +1,48 @@ +/* + SPDX-FileCopyrightText: 2015 Bernhard Friedreich + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include +#include +#include + +class Timer : public QObject +{ + Q_OBJECT + +public: + Q_INVOKABLE void runCommand(const QString &command) + { + if (!command.isEmpty()) { + QStringList split = QProcess::splitCommand(command); + const QString program = split.takeFirst(); + QProcess::startDetached(program, split); + } + } + /*! + * Represent \a seconds as s string representing time duration with + * given format based on QTime. + */ + Q_INVOKABLE QString secondsToString(int seconds, const QString &format) + { + return QTime::fromMSecsSinceStartOfDay(seconds * 1000).toString(format); + } +}; + +class TimerPlugin : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") + +public: + void registerTypes(const char *uri) override + { + qmlRegisterSingletonType(uri, 0, 1, "Timer", [](QQmlEngine *, QJSEngine *) { + return new Timer(); + }); + } +}; + +#include "timerplugin.moc" diff --git a/applets/timer/timer.svgz b/applets/timer/timer.svgz new file mode 100644 index 0000000000000000000000000000000000000000..4364927d05eff6eb387a3d3824b3746d45652efc GIT binary patch literal 7503 zcmZvfbx;)0+wTQI!X>06C8QRqrMnvz5D=G;S~{hXSW;?Px>LGKx=WVskfo(VKwA3x zd*8Wt?tSN;`OKO5&iT%B&L7X5?>w|{>}UVIj(iQA42R-&?(T7`?l+L}JuGy$ucW`T1qOTE$gp~M+jLT8gx7lhx)}Zu{u3@J__G+p?be>zAC%CQ z8e)83F$^pqF;d6JAoL9;cC}k-o8YTP!GOC7EsS=|JP@ zZXqXj)R=a*`(FI%^n=ev{&3Vhc>n03>EWrdk22tJ#PcH;*_!(VX1m^U=Hrs_<5A-& z#;^Mw-{7&u8{&+WS6^JOyqzDee)n(Ox*;?kM8E9{uKAy{>!7LfC)lp9ad8^K{Acun zkaXiwl=;6l?lY_$Krxc@xZ+)7Y#4e1m4)^KrB2r(UA|M^4fBGpt6zVdw109ATVJX> zWEOgOJiFYuIN60Koc9yJKNg>GRX?uU_}pI2p)|OvIoCRbC9|-LyQ+Q$eLYtsKXRr%PESpOn?~0f zE1h0*6`9j;WHc_{^YvS0=p;*vEuQ^c>Uvlh7x9Lf|CUmQJcu4K-rFcEh0hQyE1$<8 z_FzH#74t*ebNwT5k7$hNwlxARQIMd}2eB@)w#|i)QN;Cfl zN2wI?XPk~OZ}l!ayrzPc5i{vF$Zsvo-zfEt{B6I**2SLqN8n+n#EQqcY`#p~1gtZM z(7!6j<4XjQe`pHp18m?#7g5{gd291DKjnDn<^LW8lhPSCR$E*0e*4X}M4V2fNRmb5 z6rOj!^7pnRlEd?9ewCKv+?b^D%NqxGBMu|Jr@D_P6R_4!QXYY46J1^U7oppNq8r{$ z3$B*3Yhut>Qp9kBi(&=-mjFCy(RNln0#~$IVa&|9D&*mEtRH;hwfc#x>QU^7@^Lq# z%ZTe(^I`ko_n5oWd!uVyiHF;=>+minbTa0oP2!-`dq?92UVr%}`PIY5S%;=l+4S4w zpFc3rKL{#)d2JJHi<4cik&^WXq%PrIKWjGKQ6%+BPG|^(4r&GZUD_2MLyO1~PUalq zOxQjQV3aBgEjK>R`pv3scC@Nx(rYbQumH$OvTrSlD0ht>iZCrRJ$!@c%7RLbjjD!> z{1ouxE3^jKv3t{REQ-=MjI{9vW8dYkAOnyL#zsXH$DR*vbOXb`c=BVpc&F^Us3y!= z$7iq7(aS^qzC`T!xTl3kSxA z#DO%2BWm@3#QhUF%OZ#D9_)&I19|n2=PY;%)!2}=xk2S$VQFc}Eo)!9rK4099q@@{ zF8eLgCt-p%X!1(K14j5{y2d-4VC!lFe9RcR0{bQ=?8vQ@WE=KlpHE^Dfz*EbFG$)U z52~YlWD^gtNEKG~8IO!$EmA+{w4&0kGt`6snA%MI-OA3LMAs9#_B(6>gE zG47ej|ETw6`@hnLR@uYbPh#E8HDd@1JX|Eb3}bN^pL|G$fq|JPMTAliRNzXP*C4ry+^%H6EA<}wuF zb*IT-7IWNVaEIxf+C4AyG*#mZSfVC7o<2UQanv)#xyqI{H3s8YU>$Xz_IE$S=`8T( zLx6Ry11w%fIKnBz3GgAhFk@%p2LaF1;mm&Y*59Km{4KYo^ZPJ(|J@D4c9T^T^!f-j z(%+1n(e)SX)WusfGRoqrng>gCHm2(e`XLK<92Z>9eq1)vcs3dP`+LA{I>Ro)5KqnR zzT%f`y6-%1YaZl{=lE2mh`VOy&*TVaM>qSBov2&K)@$E0$Nk)b6 znS<(_xYim}@+TsX@B7bfb|l+w9{LaQPu%41x~{?d)f&5@;0ZTs{;?s~kCRSD-?xxV6Xn@4mKtglqqzKz{u7_W zQeO!MEY*Jwt*1j-8!&xfhQzY@e_nGW8r<@|4qf*=LhT-%udQaGpO>$JE6>G8!Mk1J zZ8wjcbt7+7e!hN!h_TEyNj^H>i!Ww(r4en+5q*&a=*iOlq`Q>GyB+>Dx4p*V=tNe& zma@4o+|W$QU2Ah9`E<21HpYDA&&9PWb`qs;SUHt;GI`im_w#i%%=~lIAdlEAvT(%Wu;<)F`<@0QATJTF~r_d8$xG! zI9?ZBCU7x@N<`xF5kduS$fy*kWa45SwbFxmk{w*!FrrM>l8?~iSPk@B17BK))r5zApGTudsqF zXZR$8AsIyY(xB}SGzAJlo!GoN0B;XvKB*^kdl+CppWAA1CU^CU+UDU#UGcPN0p;*-Fj> za^9ZVCl(jmRbQizKkccUeA=V#ov~ed9WQkC*rMpqefvCFl-X}K9`CT=6~uWuhw^c%YxT>bC;(EHaLLLbz#xe=B)uuRl-y2$qG7RWse}&C!>@x zJR$D-_aSmK);9o-l}(7eln^|c9fv31tp&_IlGRI;)rR z-DIJ>HQ}(u%F=tZ2wp1r&#zLD=&bGAO2XL0Hc%8%LM7;pNc3t5;ePKxJZ44EGPyLJ zihw0yXsMlA11P&9jc=_0V61H>joreB>}DlJP{;x2QVqou{H)bAn^(29J}WKl#gZf= za9C}ep+b4C@Io~sRV+2r)dDqioSg`*K%RcZH`c1B^h`gLidUt-DZfFy_{`%@NJE~` zkc)1mYF8m&T!RPg{bZ{WMA3j@an&QJPV93)E&O4yTY& zu@EZRSEJ%BA$Q)6lZ3tGPj;jFn#PS6B#R}(tp7GYzOJYfT|w;ZJ)N$AXw1Oe55$X9 zxLQk!rZ6ynP->QpuZWQSZQt`B&T^B=-BBu3V?8q|3U$ibM8O8@A^cfvAE1~d@AC8|HlAY)qA5B^jpM>0)nj&gK=TPx$R#gFsmoKm2$g%?IX$Un zEe{HW6I<%+>psC?zoXd`M+>6_(aGled29Zmhu3UDxq2ftuDXYCe!Ra}kPi%DKo(zP ze5bRU?baHJ5yF@yX{p5+NVH`Sz#EDAgh@_ecaVhvIheMVO0*DQ4%ku{a6~_NxcLz)Zi=Hzy-Xhb+4Nc+)1%=Mg||7$^HRmoFH_Wnq*0=uu*ds{ ziso32WyzgNfX&v6G;BJvp@5M5Bd9td(IpbwExty?>U@1m!P$C2Ut7jI9`FgHzY-K! zF&&Ct(W35?OaTb2Pt3)`Eby|-$2UjvM88&2WoD>To4&k>s8y*a#~^!U zH!7XxDy7^n+-}mV)&!}DUe(dENti}YWxdo@i<(u2ZXy9{9AS&cCR*DFo^Ny`O2ujs zJA3&3sW5oI#~&+|nW?201CUT1I~AUEA)fF9x#Ab}-`&zIu1(JrzWljBk`wY4tzz4L z7<*4Dq~nwee9=286Dx`CB4f^=QF8G427r&k82Z6$gI67ACl5u7L~c%gX8g)uw64`C z!C6OFq`)E}y}E$CutiVVS7MDQ6hPKZSg8Q$c=dSXDiqAm@&P@LJ+p-9n4E3tvLYV( z?5_*MM)r1oh;0 zY3q^FbC60O?dwy}dh9c02i&nJeda;i$*9M9YD#w{)j4`a)o?1R79P`HsR%~|P^aol zkvg*8_FH>_4*Dsht=c^GTz64D1p3{hkQ?Nr)ha_tz~M&?YxD^YkP8*WhD6vO(15N6 z1@0BLmOF@hZtWu9$<_#0*3K_SJoAe}fmK`v3+i`LW91u%fr;x#fc?H|qjR&U9B&P| zVlbvMQ^H2=Fzv5x^cuU;g_aqjPS0YC9TUd}XwJIN8p~g+MVtK&QH~9tYcS>(^8S}; zw2X7n?-|-*0+b-Cqb+%&Ba8=D=EIuwj7VR-qE|&C3zq>6>nnAeaQI8teBOP)@qr|W zR8waLIubCg@4}Dn0bE5m*12OjlHGu2iCNjbGWs0r$s;)g=2q|=uTt2<_2xg#lFagd zpQ5=s&ite!32I{uDO{xhzi7RlO3bnGsJ*jFX?OC7sQu!ybQLDd#U=LdIYk6%pE7f% zfBzdDW0VJEDrYWCzNJC$Hf2ouM`>k&Qb|FS4QfgqSRc@E2e#j{$mDwS4}YU)MSv2q zz~s_;*0L`KNyX@P%_y^;*LcYf7G)wxV@tU}ZZXsYrfNODaBxSKepG!8SY;<7z8!=a zjqO;`N?QD>f!-_Wbk@M0)e-T6FhL)R+3Pqqg%>a8i{S739A0npwoN$|CzV}-oEf1J zj!#iA8&Ax@T>cz$aKXV{g)ChKA&~LA(fK$%Z(~3j5IW2!7D!aYE8ttSE@N+yh&`K1 z5Y^1gj%PoYA!W9GDpw+tqiNXR(;Jv?wpX`9ju9!sr_mkwVDtQv2)idd!iro7ep^E= zyiE13WGfMw2dvB0nGCJS4RNcb#NDRLgs6XF?u}^5Qyo#@erwB0KiV=sIVi3eb`feN z!-6(GNiCBYUtWu0&StA7nAaV=eHQF*xqeWPAYTf?tSD*iHxos(RNN~9@ zWBni*Qfc9#Q}>fE__Y|8RjUHO)Ac+!TXwFwT7GqtezA&p)$PXx$~pRg(d9X`eP<-E zeu5Gjiq zJ)t<2w$z7z;kkY!9igtOn;f78q^)JNPS%2AYUzG~?>mE)6SwF7eVh!K#7&FqkSG(z zg1U92+c1B`v3NPeusiM-RZ${gN#TM0MO5Y5SJ|e80szKGSJW0cZeChDhbU1EkG=K? zYe{SkDxE*M-R4?N*vEQD$j2pFT+~B-?Zb{;vGU^O`?5d|5#Sdx^0J0GY>WAek;9PF2)}CWJx|m}ofvVm_7v1;Hi3 zs+^f}xQGs#WpES*{CJp4mBmgH*h!FJL;xWR)5f88W5Lov9LZQ0y3(=&Sd~VVd_$x` zg@+|P+GB2Un!^He*7&uk(H|DWp*CFaRKrNoz7`8ocE_P&(d{PrSRH=Y;MF>8|5AgH z5LQMJz$(8BA|@7aNm8!Yn)BUDB9YxM!UNvgt~oKGtU9;BwqI4+c12 zwjcvWgPf!dI=kqLh{*!T*$@Z?i9aGPo_)@C!2si|Z8h_E(a!Z*XoBngyLhMB^whF@sHi zG1jb(9`C5ei#gK|N)b!VSx2Ba>=Qd%)I`+~=haT}T25)?LKxN-dO^M?He0w^6l%ia zko``lIB-ac6LhNc?PAzsC)WZmdnalZ5=c2P9FxTJ)ouvc01Ez`9~U(kl+ov4a=7QJ z2Gdz9AbF#0Gz!?QUm;<~tcq(W%_UOexw9XlBwlU&Qly4H4p`kHM=Np6h?i9uWSOdq zq;TkPo(%==V-7P(P>GwKv(=uBs|krZTMGI3XkSpL=nV+N>NwgMI$V7+yq>Of3BUKR ztc-7WHo4xk7{M!iJe$1N`n+YxOp-;pxP-jq=H5Ke|BVg1l=(ZjWIzx5zWXJrJI-i8 zt|+$TVSa_I3}e5xlRHC&gqY3bu)M+6TErB$bkja0U^0PulunmLB#)4jmH1%Y9*-&9 z5-${uoK;b5tMojo(uf`k909AS#;O$*iuBJNGzXX@2{5Bo3CsE5yL!psg#4QzxJ!2t zAUr0lWD-+^sSMfe-lu>hCInFvwSKl}O9a6v+7L4eBR!GN6sKN<=zA*PaOM^rHj5x~ zy}y>EaxVde#yRK#KmDWkjDI*IF*DX-Lj~~86$&_Jdj6JGE8ROG_$!Kgpu3<1gtmF1 zi#4Ly?pSNfju&6&$;P0%O_@EYVU0)UwT zx!ayjVD1mO2KE$O7#d^C1s6eDSAjq|CFn1@5wBdknW^xPJRyl-YiuPKe#->=Ebn4j zN0YbDHG33K(p>gkrt5oNMcDPqvDhWElru(js`)k!=C;IGE9N&&hmR?2%!H8A!J}oR zD>buf=T1!{=9zQJu``B3n7lOk6`H0R1I`RveW~ZZalZFBN0NA9RDiIUE3)1`N(w%g zp2aBN6sN+P|INDyk=DcI!oqp~huq`XoS2c&&KP5FD-$?po5W$%z0XF>{7tO^Y0rDt zi2XKK8f8Wf=xqT~_<8hKFti2;-`Nj`yb5=)T;mYP`v%zRuT=)3C4?+*v7(!0*K=gp zjRHDL8L=37#u}Gp+AS{CgnX=a1bv*7#qNCOo$*KQN2t3$<*qh7ylQ`PXOOW}YIW@s z3ApN9_OEJ+*m&#{Z$J4QgDv;oVTm>pyWgf$(!XZmx%xHaK2GD`Hk5q~q$i*8DhiRR zunq|reYQKeuyqIG!{ZBfPdIUO+o`d`HQCm{BfqEe%hQ~HknIOa zF%?E@um*~!?UxT!RWj`pKwj`)#-&JuC1>YF{x{yk4GoYp;Ox8^@X@fLL84Y9`PkIm zH!;YN{j!Fiht>zdE=IC-TdfldFU(ev=G48n$80&y0g*Tp@FMedPR;tU#>o3)B%ph_ zkw57>^S=~|G+`@&Ds$ z@53O3BS+z%^~um+l! zDG@|c=Gf&E=ZM@b^@&d_vS}5aena3(-cluvhA~)lX~pp%eA(oO`EFca1MjFFTrGx` zNQl1v{)-W)R}%lGT0M>XvCMtAC=+vWg{B+vYe zueX-})w)&N!-lY{t;aBbp06W;xaHbHI5KbF@P`pFv38|WN9S}&-)u@|^r3&S8Nnqk z{CXcQOnpkeL=5+yJsP@rz_iz3-~YLgP5lsmG(>ydAU*{JVYn9h{&0@FV%=oetZ`Ty dXfBx$=S0R-YgGI1(LNCnk<>AoZa#bVzX1HIyjuVO literal 0 HcmV?d00001 diff --git a/applets/userswitcher/Messages.sh b/applets/userswitcher/Messages.sh new file mode 100755 index 00000000..12f8cbd0 --- /dev/null +++ b/applets/userswitcher/Messages.sh @@ -0,0 +1,3 @@ +#! /usr/bin/env bash +$XGETTEXT `find . -name \*.qml -o -name \*.js` -o $podir/plasma_applet_org.kde.plasma.userswitcher.pot +rm -f rc.cpp diff --git a/applets/userswitcher/package/contents/config/config.qml b/applets/userswitcher/package/contents/config/config.qml new file mode 100644 index 00000000..ea444e04 --- /dev/null +++ b/applets/userswitcher/package/contents/config/config.qml @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2015 Kai Uwe Broulik + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + */ + +import org.kde.plasma.configuration 2.0 + +ConfigModel { + ConfigCategory { + name: i18nc("@title", "General") + icon: "preferences-desktop-user" + source: "configGeneral.qml" + } +} diff --git a/applets/userswitcher/package/contents/config/main.xml b/applets/userswitcher/package/contents/config/main.xml new file mode 100644 index 00000000..66fa2cad --- /dev/null +++ b/applets/userswitcher/package/contents/config/main.xml @@ -0,0 +1,23 @@ + + + + + + + false + + + true + + + true + + + false + + + + diff --git a/applets/userswitcher/package/contents/ui/ActionListDelegate.qml b/applets/userswitcher/package/contents/ui/ActionListDelegate.qml new file mode 100644 index 00000000..17c1a4d3 --- /dev/null +++ b/applets/userswitcher/package/contents/ui/ActionListDelegate.qml @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2022 ivan (@ratijas) tkachenko + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.15 + +import org.kde.kirigami 2.20 as Kirigami + +ListDelegate { + id: item + + activeFocusOnTab: true + + iconItem: Kirigami.Icon { + anchors.fill: parent + source: item.icon.name + } +} diff --git a/applets/userswitcher/package/contents/ui/ListDelegate.qml b/applets/userswitcher/package/contents/ui/ListDelegate.qml new file mode 100644 index 00000000..f992a16e --- /dev/null +++ b/applets/userswitcher/package/contents/ui/ListDelegate.qml @@ -0,0 +1,76 @@ +/* + * SPDX-FileCopyrightText: 2015 Kai Uwe Broulik + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.15 +import QtQuick.Layouts 1.15 + +import org.kde.kirigami 2.20 as Kirigami +import org.kde.plasma.core as PlasmaCore +import org.kde.kirigami 2.20 as Kirigami +import org.kde.plasma.components 3.0 as PlasmaComponents3 + +PlasmaComponents3.ItemDelegate { + id: item + + Layout.fillWidth: true + + property alias subText: sublabel.text + property alias iconItem: iconItem.children + + highlighted: activeFocus + + Accessible.name: `${text}${subText ? `: ${subText}` : ""}` + + onHoveredChanged: if (hovered) { + if (ListView.view) { + ListView.view.currentIndex = index; + } + forceActiveFocus(); + } + + contentItem: RowLayout { + id: row + + spacing: Kirigami.Units.smallSpacing + + Item { + id: iconItem + + Layout.preferredWidth: Kirigami.Units.iconSizes.medium + Layout.preferredHeight: Kirigami.Units.iconSizes.medium + Layout.minimumWidth: Layout.preferredWidth + Layout.maximumWidth: Layout.preferredWidth + Layout.minimumHeight: Layout.preferredHeight + Layout.maximumHeight: Layout.preferredHeight + } + + ColumnLayout { + id: column + Layout.fillWidth: true + spacing: 0 + + PlasmaComponents3.Label { + id: label + Layout.fillWidth: true + text: item.text + textFormat: Text.PlainText + wrapMode: Text.NoWrap + elide: Text.ElideRight + } + + PlasmaComponents3.Label { + id: sublabel + Layout.fillWidth: true + textFormat: Text.PlainText + wrapMode: Text.NoWrap + elide: Text.ElideRight + opacity: 0.6 + font: Kirigami.Theme.smallFont + visible: text !== "" + } + } + } +} diff --git a/applets/userswitcher/package/contents/ui/UserListDelegate.qml b/applets/userswitcher/package/contents/ui/UserListDelegate.qml new file mode 100644 index 00000000..0586dfe8 --- /dev/null +++ b/applets/userswitcher/package/contents/ui/UserListDelegate.qml @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2022 ivan (@ratijas) tkachenko + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick +import QtQml + +import org.kde.config as KConfig +import org.kde.kcmutils as KCMUtils +import org.kde.kirigami as Kirigami +import org.kde.kirigamiaddons.components as KirigamiComponents + +ListDelegate { + id: item + + property alias source: avatar.source + + iconItem: KirigamiComponents.AvatarButton { + id: avatar + + anchors.fill: parent + + name: item.text + + // don't block mouse hover from the underlying ListView highlight + enabled: KConfig.KAuthorized.authorizeControlModule("kcm_users") + + onClicked: KCMUtils.KCMLauncher.openSystemSettings("kcm_users") + } +} diff --git a/applets/userswitcher/package/contents/ui/configGeneral.qml b/applets/userswitcher/package/contents/ui/configGeneral.qml new file mode 100644 index 00000000..f0417c95 --- /dev/null +++ b/applets/userswitcher/package/contents/ui/configGeneral.qml @@ -0,0 +1,110 @@ +/* + * SPDX-FileCopyrightText: 2015 Kai Uwe Broulik + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + */ + +import QtQuick 2.15 +import QtQuick.Controls 2.15 as QtControls + +import org.kde.kirigami 2.20 as Kirigami +import org.kde.kcmutils as KCM + +KCM.SimpleKCM { + property bool cfg_showFace + property bool cfg_showName + property bool cfg_showFullName + property alias cfg_showTechnicalInfo: showTechnicalInfoCheck.checked + + Kirigami.FormLayout { + QtControls.ButtonGroup { + id: nameGroup + } + + QtControls.RadioButton { + id: showFullNameRadio + + Kirigami.FormData.label: i18nc("@title:label", "Username style:") + + QtControls.ButtonGroup.group: nameGroup + text: i18nc("@option:radio", "Full name (if available)") + checked: cfg_showFullName + onClicked: if (checked) cfg_showFullName = true; + } + + QtControls.RadioButton { + QtControls.ButtonGroup.group: nameGroup + text: i18nc("@option:radio", "Login username") + checked: !cfg_showFullName + onClicked: if (checked) cfg_showFullName = false; + } + + + Item { + Kirigami.FormData.isSection: true + } + + + QtControls.ButtonGroup { + id: layoutGroup + } + + QtControls.RadioButton { + id: showOnlyNameRadio + + Kirigami.FormData.label: i18nc("@title:label", "Show:") + + QtControls.ButtonGroup.group: layoutGroup + text: i18nc("@option:radio", "Name") + checked: cfg_showName && !cfg_showFace + onClicked: { + if (checked) { + cfg_showName = true; + cfg_showFace = false; + } + } + } + + QtControls.RadioButton { + id: showOnlyFaceRadio + + QtControls.ButtonGroup.group: layoutGroup + text: i18nc("@option:radio", "User picture") + checked: !cfg_showName && cfg_showFace + onClicked: { + if (checked) { + cfg_showName = false; + cfg_showFace = true; + } + } + } + + QtControls.RadioButton { + id: showBothRadio + + QtControls.ButtonGroup.group: layoutGroup + text: i18nc("@option:radio", "Name and user picture") + checked: cfg_showName && cfg_showFace + onClicked: { + if (checked) { + cfg_showName = true; + cfg_showFace = true; + } + } + } + + + Item { + Kirigami.FormData.isSection: true + } + + + QtControls.CheckBox { + id: showTechnicalInfoCheck + + Kirigami.FormData.label: i18nc("@title:label", "Advanced:") + + text: i18nc("@option:check", "Show technical session information") + } + } +} diff --git a/applets/userswitcher/package/contents/ui/main.qml b/applets/userswitcher/package/contents/ui/main.qml new file mode 100644 index 00000000..69d2bc8e --- /dev/null +++ b/applets/userswitcher/package/contents/ui/main.qml @@ -0,0 +1,253 @@ +/* + * SPDX-FileCopyrightText: 2015 Kai Uwe Broulik + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 + +import org.kde.coreaddons 1.0 as KCoreAddons // kuser +import org.kde.kirigami 2.20 as Kirigami +import org.kde.config as KConfig // KAuthorized.authorizeControlModule +import org.kde.plasma.components 3.0 as PlasmaComponents3 +import org.kde.plasma.core as PlasmaCore +import org.kde.kirigami 2.20 as Kirigami +import org.kde.kirigamiaddons.components 1.0 as KirigamiComponents +import org.kde.plasma.plasmoid 2.0 + +import org.kde.plasma.private.sessions 2.0 as Sessions + +PlasmoidItem { + id: root + + readonly property bool isVertical: Plasmoid.formFactor === PlasmaCore.Types.Vertical + readonly property bool inPanel: (Plasmoid.location === PlasmaCore.Types.TopEdge + || Plasmoid.location === PlasmaCore.Types.RightEdge + || Plasmoid.location === PlasmaCore.Types.BottomEdge + || Plasmoid.location === PlasmaCore.Types.LeftEdge) + + + readonly property string displayedName: showFullName ? kuser.fullName : kuser.loginName + + readonly property bool showFace: Plasmoid.configuration.showFace + readonly property bool showName: Plasmoid.configuration.showName + readonly property string avatarIcon: kuser.faceIconUrl.toString() + + readonly property bool showFullName: Plasmoid.configuration.showFullName + + // TTY number and X display + readonly property bool showTechnicalInfo: Plasmoid.configuration.showTechnicalInfo + + switchWidth: Kirigami.Units.gridUnit * 10 + switchHeight: Kirigami.Units.gridUnit * 12 + + toolTipTextFormat: Text.StyledText + toolTipSubText: i18n("You are logged in as %1", displayedName) + + // revert to the Plasmoid icon if no face given + Plasmoid.icon: kuser.faceIconUrl.toString() || (inPanel ? "system-switch-user-symbolic" : "preferences-system-users" ) + + KCoreAddons.KUser { + id: kuser + } + + compactRepresentation: MouseArea { + id: compactRoot + + // Taken from DigitalClock to ensure uniform sizing when next to each other + readonly property bool tooSmall: Plasmoid.formFactor === PlasmaCore.Types.Horizontal && Math.round(2 * (compactRoot.height / 5)) <= Kirigami.Theme.smallFont.pixelSize + + Layout.minimumWidth: isVertical ? 0 : compactRow.implicitWidth + Layout.maximumWidth: isVertical ? Infinity : Layout.minimumWidth + Layout.preferredWidth: isVertical ? -1 : Layout.minimumWidth + + Layout.minimumHeight: isVertical ? label.height : Kirigami.Theme.smallFont.pixelSize + Layout.maximumHeight: isVertical ? Layout.minimumHeight : Infinity + Layout.preferredHeight: isVertical ? Layout.minimumHeight : Kirigami.Units.iconSizes.sizeForLabels * 2 + + property bool wasExpanded + onPressed: wasExpanded = root.expanded + onClicked: root.expanded = !wasExpanded + + Row { + id: compactRow + + anchors.centerIn: parent + spacing: Kirigami.Units.smallSpacing + + KirigamiComponents.Avatar { + id: icon + + anchors.verticalCenter: parent.verticalCenter + height: compactRoot.height - Math.round(Kirigami.Units.smallSpacing / 2) + width: height + + name: root.displayedName + + source: visible ? root.avatarIcon : "" + visible: root.showFace + } + + PlasmaComponents3.Label { + id: label + + width: root.isVertical ? compactRoot.width : contentWidth + height: root.isVertical ? contentHeight : compactRoot.height + + text: root.displayedName + textFormat: Text.PlainText + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + wrapMode: Text.NoWrap + fontSizeMode: root.isVertical ? Text.HorizontalFit : Text.VerticalFit + font.pixelSize: tooSmall ? Kirigami.Theme.defaultFont.pixelSize : Kirigami.Units.iconSizes.roundedIconSize(Kirigami.Units.gridUnit * 2) + minimumPointSize: Kirigami.Theme.smallFont.pointSize + visible: root.showName + } + } + } + + fullRepresentation: Item { + id: fullRoot + + implicitHeight: column.implicitHeight + implicitWidth: column.implicitWidth + + Layout.preferredWidth: Kirigami.Units.gridUnit * 12 + Layout.preferredHeight: implicitHeight + Layout.minimumWidth: Layout.preferredWidth + Layout.minimumHeight: Layout.preferredHeight + Layout.maximumWidth: Layout.preferredWidth + Layout.maximumHeight: Screen.height / 2 + + Sessions.SessionManagement { + id: sm + } + + Sessions.SessionsModel { + id: sessionsModel + } + + ColumnLayout { + id: column + + anchors.fill: parent + spacing: 0 + + UserListDelegate { + id: currentUserItem + text: root.displayedName + subText: i18n("Current user") + source: root.avatarIcon + hoverEnabled: false + } + + PlasmaComponents3.ScrollView { + id: scroll + + Layout.fillWidth: true + Layout.fillHeight: true + + // HACK: workaround for https://bugreports.qt.io/browse/QTBUG-83890 + PlasmaComponents3.ScrollBar.horizontal.policy: PlasmaComponents3.ScrollBar.AlwaysOff + + ListView { + id: userList + model: sessionsModel + + focus: true + interactive: true + keyNavigationWraps: true + + delegate: UserListDelegate { + width: ListView.view.width + + activeFocusOnTab: true + + text: { + if (!model.session) { + return i18nc("Nobody logged in on that session", "Unused") + } + + if (model.realName && root.showFullName) { + return model.realName + } + + return model.name + } + source: { + model.icon !== "" + ? "file://" + model.icon + : "" + } + subText: { + if (!root.showTechnicalInfo) { + return "" + } + + if (model.isTty) { + return i18nc("User logged in on console number", "TTY %1", model.vtNumber) + } else if (model.displayNumber) { + return i18nc("User logged in on console (X display number)", "on %1 (%2)", model.vtNumber, model.displayNumber) + } + return "" + } + + KeyNavigation.up: index === 0 ? currentUserItem.nextItemInFocusChain() : userList.itemAtIndex(index - 1) + KeyNavigation.down: index === userList.count - 1 ? newSessionButton : userList.itemAtIndex(index + 1) + + Accessible.description: i18nc("@action:button", "Switch to User %1", text) + + onClicked: sessionsModel.switchUser(model.vtNumber, sessionsModel.shouldLock) + } + } + } + + ActionListDelegate { + id: newSessionButton + text: i18nc("@action", "New Session") + icon.name: "system-switch-user" + visible: sessionsModel.canStartNewSession + + KeyNavigation.up: userList.count > 0 ? userList.itemAtIndex(userList.count - 1) : currentUserItem.nextItemInFocusChain() + KeyNavigation.down: lockScreenButton + + onClicked: sessionsModel.startNewSession(sessionsModel.shouldLock) + } + + ActionListDelegate { + id: lockScreenButton + text: i18nc("@action", "Lock Screen") + icon.name: "system-lock-screen" + visible: sm.canLock + + KeyNavigation.up: newSessionButton + KeyNavigation.down: leaveButton + + onClicked: sm.lock() + } + + ActionListDelegate { + id: leaveButton + text: i18nc("Show a dialog with options to logout/shutdown/restart", "Show Logout Screen") + icon.name: "system-log-out" + visible: sm.canLogout + + KeyNavigation.up: lockScreenButton + + onClicked: sm.requestLogoutPrompt() + } + } + + Connections { + target: root + function onExpandedChanged() { + if (root.expanded) { + sessionsModel.reload(); + } + } + } + } +} diff --git a/applets/userswitcher/package/metadata.json b/applets/userswitcher/package/metadata.json new file mode 100644 index 00000000..fdf4b039 --- /dev/null +++ b/applets/userswitcher/package/metadata.json @@ -0,0 +1,146 @@ +{ + "KPackageStructure": "Plasma/Applet", + "KPlugin": { + "Authors": [ + { + "Email": "kde@privat.broulik.de", + "Name": "Kai Uwe Broulik", + "Name[ar]": "كاي أووي بروتيك", + "Name[az]": "Kai Uwe Broulik", + "Name[bg]": "Kai Uwe Broulik", + "Name[ca@valencia]": "Kai Uwe Broulik", + "Name[ca]": "Kai Uwe Broulik", + "Name[cs]": "Kai Uwe Broulik", + "Name[da]": "Kai Uwe Broulik", + "Name[de]": "Kai Uwe Broulik", + "Name[en_GB]": "Kai Uwe Broulik", + "Name[eo]": "Kai Uwe Broulik", + "Name[es]": "Kai Uwe Broulik", + "Name[eu]": "Kai Uwe Broulik", + "Name[fi]": "Kai Uwe Broulik", + "Name[fr]": "Kai Uwe Broulik", + "Name[gl]": "Kai Uwe Broulik", + "Name[he]": "קאי אווה ברוליק", + "Name[hu]": "Kai Uwe Broulik", + "Name[ia]": "Kai Uwe Broulik", + "Name[id]": "Kai Uwe Broulik", + "Name[is]": "Kai Uwe Broulik", + "Name[it]": "Kai Uwe Broulik", + "Name[ja]": "Kai Uwe Broulik", + "Name[ka]": "კაი უვე ბროულიკი", + "Name[ko]": "Kai Uwe Broulik", + "Name[lt]": "Kai Uwe Broulik", + "Name[lv]": "Kai Uwe Broulik", + "Name[nl]": "Kai Uwe Broulik", + "Name[nn]": "Kai Uwe Broulik", + "Name[pl]": "Kai Uwe Broulik", + "Name[pt]": "Kai Uwe Broulik", + "Name[pt_BR]": "Kai Uwe Broulik", + "Name[ro]": "Kai Uwe Broulik", + "Name[ru]": "Kai Uwe Broulik", + "Name[sk]": "Kai Uwe Broulik", + "Name[sl]": "Kai Uwe Broulik", + "Name[sv]": "Kai Uwe Broulik", + "Name[tr]": "Kai Uwe Broulik", + "Name[uk]": "Kai Uwe Broulik", + "Name[vi]": "Kai Uwe Broulik", + "Name[x-test]": "xxKai Uwe Broulikxx", + "Name[zh_CN]": "Kai Uwe Broulik", + "Name[zh_TW]": "Kai Uwe Broulik" + } + ], + "BugReportUrl": "https://bugs.kde.org/enter_bug.cgi?product=kdeplasma-addons&component=User Switcher", + "Category": "System Information", + "Description": "Quickly switch between different users", + "Description[ar]": "بدل بسرعة بين المستخدمين", + "Description[az]": "Müxtəlif istifadəçilər arasında sürətli keçid", + "Description[bg]": "Бързо превключване между различни потребители", + "Description[ca@valencia]": "Canvia ràpidament entre diferents usuaris", + "Description[ca]": "Commuta ràpidament entre diferents usuaris", + "Description[cs]": "Rychlé přepínání mezi různými uživateli", + "Description[da]": "Skift hurtigt mellem forskellige brugere", + "Description[de]": "Schnell zwischen verschiedenen Benutzern wechseln", + "Description[en_GB]": "Quickly switch between different users", + "Description[eo]": "Rapide ŝanĝi inter malsamaj uzantoj", + "Description[es]": "Cambio rápido entre distintos usuarios", + "Description[eu]": "Erabiltzaile ezberdinen artean azkar aldatu", + "Description[fi]": "Vaihda nopeasti käyttäjätunnukselta toiselle", + "Description[fr]": "Basculer rapidement entre différents utilisateurs", + "Description[gl]": "Cambia rapidamente de usuario.", + "Description[he]": "מעבר מהיר בין משתמשים שונים", + "Description[hu]": "Gyors váltás különböző felhasználók között", + "Description[ia]": "Rapide commutation inter usatores differente", + "Description[id]": "Beralih selekasnya antara pengguna yang berbeda", + "Description[is]": "Skiptu snöggt á milli mismunandi notenda ", + "Description[it]": "Passa rapidamente tra i diversi utenti", + "Description[ja]": "他のユーザに素早く切り替え", + "Description[ka]": "სწრაფად გადაერთეთ სხვადასხვა მომხმარებლებს შორის", + "Description[ko]": "사용자를 빠르게 전환", + "Description[lt]": "Greitai persijungti tarp skirtingų sistemos naudotojų", + "Description[lv]": "Ātra pārslēgšanās starp dažādiem lietotājiem", + "Description[nl]": "Snel wisselen tussen verschillende gebruikers", + "Description[nn]": "Byt kjapt mellom ulike brukarar", + "Description[pl]": "Przełącza między użytkownikami", + "Description[pt]": "Mudar rapidamente entre os diferentes utilizadores", + "Description[pt_BR]": "Alterna rapidamente entre os usuários", + "Description[ro]": "Schimbă rapid între diferiți utilizatori", + "Description[ru]": "Быстрое переключение между пользователями", + "Description[sk]": "Rýchlo prepínajte medzi rôznymi používateľmi", + "Description[sl]": "Hitro preklapljaj med različnimi uporabniki", + "Description[sv]": "Byt snabbt mellan olika användare", + "Description[tr]": "Farklı kullanıcılar arasında tez geçiş yap", + "Description[uk]": "Швидке перемикання між записами користувачів", + "Description[vi]": "Chuyển nhanh giữa những người dùng khác nhau", + "Description[x-test]": "xxQuickly switch between different usersxx", + "Description[zh_CN]": "在不同用户之间快速切换", + "Description[zh_TW]": "快速的在不同使用者間切換", + "Icon": "preferences-desktop-user", + "Id": "org.kde.plasma.userswitcher", + "License": "GPL-2.0+", + "Name": "User Switcher", + "Name[ar]": "مبدل المستخدم", + "Name[az]": "İstifadəçi dəyişdiricisi", + "Name[bg]": "Превключване на потребители", + "Name[ca@valencia]": "Commutador d'usuaris", + "Name[ca]": "Commutador d'usuaris", + "Name[cs]": "Přepínání uživatelů", + "Name[da]": "Brugerskifter", + "Name[de]": "Benutzerwechsel", + "Name[en_GB]": "User Switcher", + "Name[eo]": "Uzanto Ŝaltilo", + "Name[es]": "Selector de usuario", + "Name[eu]": "Erabiltzaile-trukatzailea", + "Name[fi]": "Käyttäjävaihto", + "Name[fr]": "Commutateur d'utilisateurs", + "Name[gl]": "Selector de usuario", + "Name[he]": "מחליף משתמשים", + "Name[hu]": "Felhasználóváltó", + "Name[ia]": "Commutator de usator", + "Name[id]": "Pengalih Pengguna", + "Name[is]": "Skipt um notanda", + "Name[it]": "Cambia utente", + "Name[ja]": "ユーザスイッチャー", + "Name[ka]": "მომხმარებლის გადამრთველი", + "Name[ko]": "사용자 전환기", + "Name[lt]": "Naudotojų perjungiklis", + "Name[lv]": "Lietotāju pārslēdzējs", + "Name[nl]": "Wisselen van gebruiker", + "Name[nn]": "Brukar­bytar", + "Name[pl]": "Przełącznik użytkowników", + "Name[pt]": "Selector de Utilizadores", + "Name[pt_BR]": "Seletor de usuário", + "Name[ro]": "Comutator de utilizatori", + "Name[ru]": "Переключатель пользователей", + "Name[sk]": "Prepínač používateľov", + "Name[sl]": "Preklopnik uporabnikov", + "Name[sv]": "Användarbytare", + "Name[tr]": "Kullanıcı Değiştiricisi", + "Name[uk]": "Перемикання користувачів", + "Name[vi]": "Trình chuyển người dùng", + "Name[x-test]": "xxUser Switcherxx", + "Name[zh_CN]": "用户切换器", + "Name[zh_TW]": "使用者切換", + "Website": "https://kde.org/plasma-desktop" + }, + "X-Plasma-API-Minimum-Version": "6.0" +} diff --git a/applets/weather/CMakeLists.txt b/applets/weather/CMakeLists.txt new file mode 100644 index 00000000..8bb7b923 --- /dev/null +++ b/applets/weather/CMakeLists.txt @@ -0,0 +1,30 @@ +add_definitions(-DTRANSLATION_DOMAIN=\"plasma_applet_org.kde.plasma.weather\") + +kcoreaddons_add_plugin(org.kde.plasma.weather SOURCES weatherapplet.cpp INSTALL_NAMESPACE "plasma/applets") + +target_link_libraries(org.kde.plasma.weather + Plasma::Plasma + Plasma::Plasma5Support + KF6::UnitConversion + KF6::I18n +) + +install(FILES wind-arrows.svgz DESTINATION ${PLASMA_DATA_INSTALL_DIR}/desktoptheme/default/weather/) +plasma_install_package(package org.kde.plasma.weather) + +ecm_add_qml_module(weatherplugin URI org.kde.plasma.private.weather) + +target_sources(weatherplugin PRIVATE + plugin/plugin.cpp + plugin/util.cpp + plugin/abstractunitlistmodel.cpp + plugin/locationlistmodel.cpp +) +target_link_libraries(weatherplugin PRIVATE + Plasma::Plasma + Plasma::Plasma5Support + KF6::UnitConversion + KF6::I18n + Qt::Qml +) +ecm_finalize_qml_module(weatherplugin) diff --git a/applets/weather/Messages.sh b/applets/weather/Messages.sh new file mode 100755 index 00000000..7fc74dac --- /dev/null +++ b/applets/weather/Messages.sh @@ -0,0 +1,11 @@ +#! /usr/bin/env bash +for file in data/*.dat +do + awk -F'|' '$0 ~ /\|/ { + print "// i18n: file: '`basename $file`':"NR; + printf("i18nc(\"%s\", \"%s\");\n", $1, $2) + }' $file >> rc.cpp +done + +$XGETTEXT `find . -name \*.js -o -name \*.qml -o -name \*.cpp` -o $podir/plasma_applet_org.kde.plasma.weather.pot +rm -f rc.cpp diff --git a/applets/weather/data/i18n.dat b/applets/weather/data/i18n.dat new file mode 100644 index 00000000..8ec79702 --- /dev/null +++ b/applets/weather/data/i18n.dat @@ -0,0 +1,18 @@ +wind direction|N +wind direction|NNE +wind direction|NE +wind direction|ENE +wind direction|E +wind direction|SSE +wind direction|SE +wind direction|ESE +wind direction|S +wind direction|NNW +wind direction|NW +wind direction|WNW +wind direction|W +wind direction|SSW +wind direction|SW +wind direction|WSW +wind direction|VR +wind speed|Calm diff --git a/applets/weather/package/contents/config/config.qml b/applets/weather/package/contents/config/config.qml new file mode 100644 index 00000000..8fe006d8 --- /dev/null +++ b/applets/weather/package/contents/config/config.qml @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: 2016 Friedrich W. H. Kossebau + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick + +import org.kde.plasma.configuration +import org.kde.plasma.plasmoid +import org.kde.plasma.core as PlasmaCore + +ConfigModel { + ConfigCategory { + name: i18nc("@title", "Weather Station") + icon: "services" + source: "config/ConfigWeatherStation.qml" + } + + ConfigCategory { + name: i18nc("@title", "Appearance") + icon: "preferences-desktop-color" + source: "config/ConfigAppearance.qml" + // This category's settings only apply to the CompactRepresentation in the panel + visible: [PlasmaCore.Types.TopEdge, PlasmaCore.Types.BottomEdge, + PlasmaCore.Types.LeftEdge, PlasmaCore.Types.RightEdge, + ].includes(Plasmoid.location) + } + + ConfigCategory { + name: i18nc("@title", "Units") + icon: "preferences-other" + source: "config/ConfigUnits.qml" + } +} diff --git a/applets/weather/package/contents/config/main.xml b/applets/weather/package/contents/config/main.xml new file mode 100644 index 00000000..e6dce7bd --- /dev/null +++ b/applets/weather/package/contents/config/main.xml @@ -0,0 +1,54 @@ + + + + + + + + 30 + + + + + + + + + false + + + false + + + + true + + + true + + + false + + + true + + + + + + + + + + + + + + + diff --git a/applets/weather/package/contents/ui/CompactRepresentation.qml b/applets/weather/package/contents/ui/CompactRepresentation.qml new file mode 100644 index 00000000..008e9d77 --- /dev/null +++ b/applets/weather/package/contents/ui/CompactRepresentation.qml @@ -0,0 +1,79 @@ +/* + * SPDX-FileCopyrightText: 2018 Friedrich W. H. Kossebau + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick + +import QtQuick.Layouts + +import org.kde.plasma.plasmoid +import org.kde.plasma.core as PlasmaCore +import org.kde.kirigami as Kirigami +import org.kde.plasma.workspace.components as WorkspaceComponents + +Loader { + id: compactRoot + + property var generalModel + property var observationModel + + readonly property bool vertical: (Plasmoid.formFactor == PlasmaCore.Types.Vertical) + readonly property bool showTemperature: Plasmoid.configuration.showTemperatureInCompactMode + readonly property bool useBadge: Plasmoid.configuration.showTemperatureInBadge || Plasmoid.needsToBeSquare + + sourceComponent: (showTemperature && !useBadge) ? iconAndTextComponent : iconComponent + Layout.fillWidth: compactRoot.vertical + Layout.fillHeight: !compactRoot.vertical + Layout.minimumWidth: item.Layout.minimumWidth + Layout.minimumHeight: item.Layout.minimumHeight + + MouseArea { + id: compactMouseArea + anchors.fill: parent + + hoverEnabled: true + + onClicked: { + root.expanded = !root.expanded; + } + } + + Component { + id: iconComponent + + Kirigami.Icon { + readonly property int minIconSize: Math.max((compactRoot.vertical ? compactRoot.width : compactRoot.height), Kirigami.Units.iconSizes.small) + + source: Plasmoid.icon + active: compactMouseArea.containsMouse + // reset implicit size, so layout in free dimension does not stop at the default one + implicitWidth: Kirigami.Units.iconSizes.small + implicitHeight: Kirigami.Units.iconSizes.small + Layout.minimumWidth: compactRoot.vertical ? Kirigami.Units.iconSizes.small : minIconSize + Layout.minimumHeight: compactRoot.vertical ? minIconSize : Kirigami.Units.iconSizes.small + + WorkspaceComponents.BadgeOverlay { + anchors.bottom: parent.bottom + anchors.right: parent.right + + visible: showTemperature && useBadge && text.length > 0 + + text: observationModel.temperature + icon: parent + } + } + } + + Component { + id: iconAndTextComponent + + IconAndTextItem { + vertical: compactRoot.vertical + iconSource: Plasmoid.icon + active: compactMouseArea.containsMouse + text: observationModel.temperature + } + } +} diff --git a/applets/weather/package/contents/ui/DetailsView.qml b/applets/weather/package/contents/ui/DetailsView.qml new file mode 100644 index 00000000..47f86b03 --- /dev/null +++ b/applets/weather/package/contents/ui/DetailsView.qml @@ -0,0 +1,44 @@ +/* + * SPDX-FileCopyrightText: 2018 Friedrich W. H. Kossebau + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick +import QtQuick.Layouts + +import org.kde.kirigami as Kirigami +import org.kde.plasma.components as PlasmaComponents + +ColumnLayout { + id: root + + required property var model + + GridLayout { + Layout.alignment: Qt.AlignCenter + + rows: labelRepeater.count + flow: GridLayout.TopToBottom + rowSpacing: Kirigami.Units.smallSpacing + + Repeater { + id: labelRepeater + model: root.model + delegate: PlasmaComponents.Label { + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + text: modelData.label + textFormat: Text.PlainText + } + } + + Repeater { + model: root.model + delegate: PlasmaComponents.Label { + Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + text: modelData.text + textFormat: Text.PlainText + } + } + } +} diff --git a/applets/weather/package/contents/ui/ForecastView.qml b/applets/weather/package/contents/ui/ForecastView.qml new file mode 100644 index 00000000..4aa00914 --- /dev/null +++ b/applets/weather/package/contents/ui/ForecastView.qml @@ -0,0 +1,141 @@ +/* + * SPDX-FileCopyrightText: 2018 Friedrich W. H. Kossebau + * SPDX-FileCopyrightText: 2022 Ismael Asensio + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick + +import QtQuick.Layouts + +import org.kde.plasma.core as PlasmaCore +import org.kde.kirigami as Kirigami +import org.kde.plasma.components as PlasmaComponents + +GridLayout { + id: root + + property alias model: repeater.model + property bool showNightRow: false + + readonly property bool startsAtNight: !model[0] // When the first item is undefined + readonly property int preferredIconSize: Kirigami.Units.iconSizes.large + readonly property bool hasContent: model && model.length > 0 + readonly property var rowHasProbability: [...Array(rows).keys()].map( + row => model.filter((_, index) => index % root.rows == row) + .some(item => item?.probability ?? false)) + + columnSpacing: 0 + rowSpacing: Kirigami.Units.largeSpacing + + rows: showNightRow ? 2 : 1 + flow: showNightRow ? GridLayout.TopToBottom : GridLayout.LeftToRight + + // Add Day/Night labels as the row headings when there is a night row + component DayNightLabel: PlasmaComponents.Label { + visible: root.showNightRow + Layout.alignment: Qt.AlignLeft | Qt.AlignTop + Layout.fillWidth: true + Layout.preferredWidth: startsAtNight ? Kirigami.Units.largeSpacing : implicitWidth + font.bold: true + } + + DayNightLabel { + text: i18nc("Time of the day (from the duple Day/Night)", "Day") + } + + DayNightLabel { + text: i18nc("Time of the day (from the duple Day/Night)", "Night") + // Save space by moving this label over the night row when possible + Layout.topMargin: startsAtNight ? -Kirigami.Units.gridUnit : 0 + } + + // Item to get the metrics of the regular font in a PlasmaComponent.Label + PlasmaComponents.Label { + id: helperLabel + visible: false + + TextMetrics { + id: labelFontMetrics + text: "99%" // We want the sizing for the regular font, not emoji + font: helperLabel.font // Explicitly use the actual Label's font even if it's the default one + } + } + + Repeater { + id: repeater + + delegate: ColumnLayout { + id: dayDelegate + + Layout.fillWidth: true + spacing: Math.round(Kirigami.Units.smallSpacing / 2) + + PlasmaComponents.Label { + id: periodLabel + Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + // Hide period titles on the second row + visible: (model.index % root.rows) === 0 + + font.bold: true + horizontalAlignment: Text.AlignHCenter + text: modelData?.period?.replace(" nt", "") || "" + textFormat: Text.PlainText + } + + Kirigami.Icon { + Layout.fillWidth: true + Layout.preferredHeight: preferredIconSize + Layout.preferredWidth: preferredIconSize + + source: modelData?.icon ?? "" + + PlasmaCore.ToolTipArea { + id: iconToolTip + anchors.fill: parent + mainText: { + if (!modelData?.condition) { + return ""; + } + if (!modelData?.probability) { + return modelData.condition; + } + return i18nc("certain weather condition (probability percentage)", + "%1 (%2%)", modelData.condition, modelData.probability); + } + } + } + + PlasmaComponents.Label { + // Position it closer to the weather condition icon + Layout.alignment: Qt.AlignTop | Qt.AlignHCenter + Layout.topMargin: -Kirigami.Units.smallSpacing + Layout.bottomMargin: Kirigami.Units.smallSpacing * 2 + // Fixed value, to prevent the emoji font from setting a larger height + Layout.preferredHeight: labelFontMetrics.height + + horizontalAlignment: Text.AlignHCenter + text: modelData?.probability ? i18nc("Probability of precipitation in percentage", "☂%1%", modelData.probability) : "·" + textFormat: Text.PlainText + visible: modelData && root.rowHasProbability[index % root.rows] + } + + PlasmaComponents.Label { + Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + horizontalAlignment: Text.AlignHCenter + text: modelData ? modelData.tempHigh || i18nc("Short for no data available", "-") : "" + textFormat: Text.PlainText + visible: modelData?.tempHigh || !showNightRow + } + + PlasmaComponents.Label { + Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + horizontalAlignment: Text.AlignHCenter + text: modelData ? modelData.tempLow || i18nc("Short for no data available", "-") : "" + textFormat: Text.PlainText + visible: modelData?.tempLow || !showNightRow + } + } + } +} diff --git a/applets/weather/package/contents/ui/FullRepresentation.qml b/applets/weather/package/contents/ui/FullRepresentation.qml new file mode 100644 index 00000000..9f2bfb49 --- /dev/null +++ b/applets/weather/package/contents/ui/FullRepresentation.qml @@ -0,0 +1,118 @@ +/* + * SPDX-FileCopyrightText: 2018 Friedrich W. H. Kossebau + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick + +import QtQuick.Layouts +import QtQuick.Controls as QQC2 + +import org.kde.plasma.plasmoid +import org.kde.plasma.extras as PlasmaExtras +import org.kde.plasma.core as PlasmaCore +import org.kde.kirigami as Kirigami +import org.kde.plasma.components as PlasmaComponents + +import org.kde.plasma.private.weather + +ColumnLayout { + id: fullRoot + + Layout.margins: Kirigami.Units.smallSpacing + + property alias generalModel: topPanel.generalModel + property alias observationModel: topPanel.observationModel + + Layout.minimumWidth: Math.max(Kirigami.Units.gridUnit * 10, implicitWidth) + Layout.minimumHeight: Math.max(Kirigami.Units.gridUnit * 10, implicitHeight) + + PlasmaExtras.PlaceholderMessage { + Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + Layout.margins: Kirigami.Units.gridUnit + // when not in panel, a configure button is already shown for needsConfiguration + visible: (root.status === Util.NeedsConfiguration) && (Plasmoid.formFactor === PlasmaCore.Types.Vertical || Plasmoid.formFactor === PlasmaCore.Types.Horizontal) + iconName: "mark-location" + text: i18n("Please set your location") + helpfulAction: QQC2.Action { + icon.name: "configure" + text: i18n("Set location…") + onTriggered: { + Plasmoid.internalAction("configure").trigger(); + } + } + } + + PlasmaExtras.PlaceholderMessage { + Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + Layout.margins: Kirigami.Units.largeSpacing * 4 + Layout.maximumWidth: Kirigami.Units.gridUnit * 20 + visible: root.status === Util.Timeout + iconName: "network-disconnect" + text: { + const sourceTokens = root.weatherSource.split("|"); + return i18n("Unable to retrieve weather information for %1", sourceTokens[2]); + } + explanation: i18nc("@info:usagetip", "The network request timed out, possibly due to a server outage at the weather station provider. Check again later.") + } + + TopPanel { + id: topPanel + visible: root.status !== Util.NeedsConfiguration && root.status !== Util.Timeout + + Layout.fillWidth: true + // Allow the top panel to vertically grow but within a limit + Layout.fillHeight: true + Layout.maximumHeight: implicitHeight * 1.5 + } + + SwitchPanel { + id: switchPanel + visible: root.status === Util.Normal + Layout.fillWidth: true + + forecastViewTitle: generalModel.forecastTitle + forecastViewNightRow: generalModel.forecastNightRow + forecastModel: root.forecastModel + detailsModel: root.detailsModel + noticesModel: root.noticesModel + } + + PlasmaComponents.Label { + id: sourceLabel + visible: root.status === Util.Normal + readonly property string creditUrl: generalModel.creditUrl + + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + + MouseArea { + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.NoButton + cursorShape: !!parent.creditUrl ? Qt.PointingHandCursor : Qt.ArrowCursor + } + + wrapMode: Text.WordWrap + horizontalAlignment: Text.AlignRight + font { + pointSize: Kirigami.Theme.smallFont.pointSize + underline: !!creditUrl + } + linkColor : color + opacity: 0.6 + textFormat: Text.StyledText + + text: { + let result = generalModel.courtesy; + if (creditUrl) { + result = "" + result + ""; + } + return result; + } + + onLinkActivated: link => { + Qt.openUrlExternally(link); + } + } +} diff --git a/applets/weather/package/contents/ui/IconAndTextItem.qml b/applets/weather/package/contents/ui/IconAndTextItem.qml new file mode 100644 index 00000000..c5c59149 --- /dev/null +++ b/applets/weather/package/contents/ui/IconAndTextItem.qml @@ -0,0 +1,114 @@ +/* + * SPDX-FileCopyrightText: 2018 Friedrich W. H. Kossebau + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick + +import QtQuick.Layouts + +import org.kde.kirigami as Kirigami +import org.kde.plasma.components as PlasmaComponents + +GridLayout { + id: iconAndTextRoot + + property alias iconSource: icon.source + property alias text: label.text + property bool vertical: false // too bad we cannot make this an enum + property alias active: icon.active + + readonly property int minimumIconSize: Kirigami.Units.iconSizes.small + readonly property int iconSize: iconAndTextRoot.vertical ? width : height + + columns: iconAndTextRoot.vertical ? 1 : 2 + rows: iconAndTextRoot.vertical ? 2 : 1 + + columnSpacing: 0 + rowSpacing: 0 + + Kirigami.Icon { + id: icon + + readonly property int implicitMinimumIconSize: Math.max(iconSize, minimumIconSize) + // reset implicit size, so layout in free dimension does not stop at the default one + implicitWidth: minimumIconSize + implicitHeight: minimumIconSize + + Layout.fillWidth: iconAndTextRoot.vertical + Layout.fillHeight: !iconAndTextRoot.vertical + Layout.minimumWidth: iconAndTextRoot.vertical ? minimumIconSize : implicitMinimumIconSize + Layout.minimumHeight: iconAndTextRoot.vertical ? implicitMinimumIconSize : minimumIconSize + } + + Item { + id: text + + // Otherwise it takes up too much space while loading + visible: label.text.length > 0 + + Layout.fillWidth: iconAndTextRoot.vertical + Layout.fillHeight: !iconAndTextRoot.vertical + Layout.minimumWidth: iconAndTextRoot.vertical ? 0 : sizehelper.paintedWidth + Layout.maximumWidth: iconAndTextRoot.vertical ? Infinity : Layout.minimumWidth + + Layout.minimumHeight: iconAndTextRoot.vertical ? sizehelper.paintedHeight : 0 + Layout.maximumHeight: iconAndTextRoot.vertical ? Layout.minimumHeight : Infinity + + Text { + id: sizehelper + + font { + family: label.font.family + weight: label.font.weight + italic: label.font.italic + pixelSize: iconAndTextRoot.vertical ? Kirigami.Units.gridUnit * 2 : 1024 // random "big enough" size - this is used as a max pixelSize by the fontSizeMode + } + minimumPixelSize: Math.round(Kirigami.Units.gridUnit / 2) + fontSizeMode: iconAndTextRoot.vertical ? Text.HorizontalFit : Text.VerticalFit + wrapMode: Text.NoWrap + + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + anchors { + leftMargin: Kirigami.Units.smallSpacing + rightMargin: Kirigami.Units.smallSpacing + } + // These magic values are taken from the digital clock, so that the + // text sizes here are identical with various clock text sizes + height: { + const textHeightScaleFactor = (parent.height > 26) ? 0.7 : 0.9; + return Math.min (parent.height * textHeightScaleFactor, 3 * Kirigami.Theme.defaultFont.pixelSize); + } + visible: false + + // pattern to reserve some constant space TODO: improve and take formatting/i18n into account + text: "888° X" + textFormat: Text.PlainText + } + + PlasmaComponents.Label { + id: label + + font { + weight: Font.Normal + pixelSize: 1024 + } + minimumPixelSize: Math.round(Kirigami.Units.gridUnit / 2) + fontSizeMode: Text.Fit + textFormat: Text.PlainText + wrapMode: Text.NoWrap + + height: 0 + width: 0 + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + anchors { + fill: parent + leftMargin: Kirigami.Units.smallSpacing + rightMargin: Kirigami.Units.smallSpacing + } + } + } +} diff --git a/applets/weather/package/contents/ui/NoticesView.qml b/applets/weather/package/contents/ui/NoticesView.qml new file mode 100644 index 00000000..0d0adc93 --- /dev/null +++ b/applets/weather/package/contents/ui/NoticesView.qml @@ -0,0 +1,92 @@ +/* + * SPDX-FileCopyrightText: 2018 Friedrich W. H. Kossebau + * SPDX-FileCopyrightText: 2023 Ismael Asensio + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls as QQC2 + +import org.kde.plasma.components as PlasmaComponents +import org.kde.kirigami as Kirigami + +ListView { + id: root + + anchors.fill: parent + boundsBehavior: Flickable.StopAtBounds + + delegate: RowLayout { + width: ListView.view.width - (scrollBar.visible ? scrollBar.width : 0) + spacing: 0 + + PlasmaComponents.Label { + Layout.alignment: Qt.AlignTop + Layout.minimumWidth: Kirigami.Units.gridUnit * 5 + Layout.maximumWidth: Kirigami.Units.gridUnit * 10 + Layout.margins: Kirigami.Units.largeSpacing + + text: modelData.timestamp + textFormat: Text.PlainText + horizontalAlignment: Text.AlignRight + wrapMode: Text.Wrap + } + + Kirigami.Icon { + Layout.alignment: Qt.AlignTop + Layout.minimumWidth: implicitWidth + implicitWidth: Kirigami.Units.iconSizes.smallMedium + source: (modelData.priority >= 3) ? 'flag-red-symbolic' : + (modelData.priority >= 2) ? 'flag-yellow-symbolic' : + 'flag-blue-symbolic' + } + + Kirigami.SelectableLabel { + Layout.fillWidth: true + Layout.alignment: Qt.AlignTop + Layout.minimumWidth: Kirigami.Units.gridUnit * 5 + Layout.margins: Kirigami.Units.largeSpacing + + text: modelData.description + wrapMode: Text.Wrap + } + + PlasmaComponents.ToolButton { + visible: !!modelData.infoUrl + Layout.alignment: Qt.AlignTop + Layout.minimumWidth: implicitWidth + icon.name: 'showinfo-symbolic' + text: i18nc("@action:button", "Show more information") + display: PlasmaComponents.ToolButton.IconOnly + onClicked: { + Qt.openUrlExternally(Qt.resolvedUrl(modelData.infoUrl)) + } + } + } + + QQC2.ScrollBar.vertical: QQC2.ScrollBar { + id: scrollBar + anchors.right: parent.right + visible: root.contentHeight > root.height + } + + Kirigami.Separator { + anchors { + top: parent.top + left: parent.left + right: parent.right + } + visible: !root.atYBeginning + } + + Kirigami.Separator { + anchors { + bottom: parent.bottom + left: parent.left + right: parent.right + } + visible: !root.atYEnd + } +} diff --git a/applets/weather/package/contents/ui/SwitchPanel.qml b/applets/weather/package/contents/ui/SwitchPanel.qml new file mode 100644 index 00000000..cc293590 --- /dev/null +++ b/applets/weather/package/contents/ui/SwitchPanel.qml @@ -0,0 +1,136 @@ +/* + * SPDX-FileCopyrightText: 2018 Friedrich W. H. Kossebau + * SPDX-FileCopyrightText: 2023 Ismael Asensio + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick + +import QtQuick.Layouts +import QtQuick.Controls as QQC2 + +import org.kde.kirigami as Kirigami +import org.kde.plasma.components as PlasmaComponents +import org.kde.plasma.extras as PlasmaExtras + +ColumnLayout { + id: root + + required property var forecastModel + required property var detailsModel + required property var noticesModel + + required property bool forecastViewNightRow + required property string forecastViewTitle + + readonly property var pagesModel: { + const pages = [{ + title: root.forecastViewTitle || i18nc("@title:tab Weather forecast", "Forecast"), + view: root.forecastModel?.length > 0 ? forecastView : forecastPlaceholder, + }] + + if (root.detailsModel && root.detailsModel.length > 0) { + pages.push({ + title: i18nc("@title:tab", "Details"), + view: detailsView, + }) + } + if (root.noticesModel && root.noticesModel.length > 0) { + pages.push({ + title: i18ncp("@title:tab %1 is the number of weather notices (alerts, warnings, watches, ...) issued", + "%1 Notice", "%1 Notices", noticesModel.length), + view: noticesView, + // Show warning icon if the maximum priority shown is at least 2 (Moderate) + icon: noticesModel.reduce((acc, notice) => Math.max(notice.priority, acc), 0) >= 2 ? + 'data-warning-symbolic' : 'data-information-symbolic', + }) + } + return pages + } + + PlasmaComponents.TabBar { + id: tabBar + + Layout.fillWidth: true + visible: root.pagesModel.length > 1 + + Repeater { + model: root.pagesModel + delegate: PlasmaComponents.TabButton { + text: modelData.title + icon.name: modelData.icon ?? "" + } + } + + onCurrentIndexChanged: { + // Avoid scrolling conflicts between SwipeView and NoticesView + swipeView.interactive = false; + swipeView.setCurrentIndex(currentIndex); + swipeView.interactive = true; + } + } + + QQC2.SwipeView { + id: swipeView + + Layout.fillWidth: true + Layout.fillHeight: true + Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + Layout.minimumWidth: contentChildren.reduce((acc, loader) => Math.max(loader.implicitWidth, acc), 0) + Layout.minimumHeight: contentChildren.reduce((acc, loader) => Math.max(loader.implicitHeight, acc), 0) + clip: true // previous/next views are prepared outside of view, do not render them + + Repeater { + model: root.pagesModel + delegate: Loader { + sourceComponent: modelData.view + } + } + + onCurrentIndexChanged: { + tabBar.setCurrentIndex(currentIndex); + } + } + + Component { + id: forecastView + ForecastView { + model: root.forecastModel + showNightRow: root.forecastViewNightRow + } + } + + Component { + id: forecastPlaceholder + ColumnLayout { + PlasmaExtras.PlaceholderMessage { + Layout.alignment: Qt.AlignCenter + Layout.margins: Kirigami.Units.largeSpacing + // Sets a minimum width for the placeholder tab + Layout.preferredWidth: Kirigami.Units.gridUnit * 15 + + iconName: "weather-none-available-symbolic" + text: i18nc("@info:placeholder", "Unable to load weather forecast") + // TODO: Add a link to the bug-report url, which is now not possible to access within the placeholder + explanation: i18nc("@info:usagetip", "There may be a technical issue with the weather provider. If the issue persists for longer than a day, submit a bug report.",) + } + } + } + + Component { + id: detailsView + DetailsView { + model: root.detailsModel + } + } + + Component { + id: noticesView + NoticesView { + model: root.noticesModel + // Avoid scrolling conflicts between SwipeView and NoticesView + interactive: swipeView.contentItem.atXEnd + } + } +} diff --git a/applets/weather/package/contents/ui/TopPanel.qml b/applets/weather/package/contents/ui/TopPanel.qml new file mode 100644 index 00000000..eb3e6e42 --- /dev/null +++ b/applets/weather/package/contents/ui/TopPanel.qml @@ -0,0 +1,151 @@ +/* + * SPDX-FileCopyrightText: 2018 Friedrich W. H. Kossebau + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick + +import QtQuick.Layouts + +import org.kde.kirigami as Kirigami +import org.kde.ksvg as KSvg +import org.kde.plasma.components as PlasmaComponents + +GridLayout { + property var generalModel + property var observationModel + + readonly property int sideWidth: Math.max( + windSpeedLabel.implicitWidth, + tempLabel.implicitWidth, + windSpeedDirection.naturalSize.width + ) + + Layout.minimumWidth: Math.max( + locationLabel.implicitWidth, + (sideWidth + columnSpacing) * 2 + Kirigami.Units.iconSizes.huge /* conditionIcon.Layout.minimumWidth */ + ) + + visible: !!generalModel.location + + columnSpacing: Kirigami.Units.largeSpacing + rowSpacing: Kirigami.Units.largeSpacing + + columns: 3 + + Kirigami.Heading { + id: locationLabel + + Layout.row: 0 + Layout.column: 0 + Layout.columnSpan: 3 + Layout.fillWidth: true + + wrapMode: Text.NoWrap + + text: generalModel.location + textFormat: Text.PlainText + } + + ColumnLayout { + Layout.row: 1 + Layout.column: 0 + Layout.fillWidth: true + Layout.preferredWidth: 25 // 25% of the view + Layout.minimumWidth: sideWidth + + spacing: Kirigami.Units.smallSpacing + + PlasmaComponents.Label { + id: tempLabel + Layout.fillWidth: true + + font.pixelSize: Kirigami.Units.iconSizes.medium + font.bold: true + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.NoWrap + textFormat: Text.PlainText + + text: observationModel.temperature + } + + PlasmaComponents.Label { + Layout.fillWidth: true + + visible: !!observationModel.feelsLikeTemperature && observationModel.feelsLikeTemperature !== observationModel.temperature + + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.Wrap + textFormat: Text.PlainText + + text: i18nc("@label %1 is the perceived temperature due to conditions like wind or humidity. Use the common phrasing for this concept and keep it short, adding a colon if necessary", + "Feels like %1", observationModel.feelsLikeTemperature || "") + } + } + + Kirigami.Icon { + id: conditionIcon + + Layout.row: 1 + Layout.column: 1 + Layout.minimumHeight: Kirigami.Units.iconSizes.huge + Layout.minimumWidth: Kirigami.Units.iconSizes.huge + Layout.preferredHeight: Layout.minimumHeight + Layout.fillWidth: true + // All the items have `fillWidth: true`, so the layout weights each + // contribution and splits the space accordingly to their proportion. + Layout.preferredWidth: 50 // 50% of the view + + source: generalModel.currentConditionIconName + } + + PlasmaComponents.Label { + id: conditionLabel + + visible: text.length > 0 + + Layout.row: 2 + Layout.column: 0 + Layout.columnSpan: 3 + Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + + text: observationModel.conditions + textFormat: Text.PlainText + } + + Item { + Layout.row: 1 + Layout.column: 2 + Layout.fillWidth: true + Layout.preferredWidth: 25 // 25% of the view + Layout.minimumWidth: sideWidth + Layout.alignment: Qt.AlignCenter + + implicitHeight: windSpeedDirection.implicitHeight + windSpeedLabel.implicitHeight + + KSvg.SvgItem { + id: windSpeedDirection + + anchors.horizontalCenter: parent.horizontalCenter + implicitWidth: Kirigami.Units.iconSizes.medium + implicitHeight: Kirigami.Units.iconSizes.medium + + imagePath: "weather/wind-arrows" + elementId: observationModel.windDirectionId || "" + + visible: !!observationModel.windDirectionId + } + + PlasmaComponents.Label { + id: windSpeedLabel + anchors { + top: windSpeedDirection.bottom + horizontalCenter: parent.horizontalCenter + } + text: observationModel.windSpeed + textFormat: Text.PlainText + } + } + +} diff --git a/applets/weather/package/contents/ui/config/ConfigAppearance.qml b/applets/weather/package/contents/ui/config/ConfigAppearance.qml new file mode 100644 index 00000000..0f55a331 --- /dev/null +++ b/applets/weather/package/contents/ui/config/ConfigAppearance.qml @@ -0,0 +1,89 @@ +/* + * SPDX-FileCopyrightText: 2018 Friedrich W. H. Kossebau + * SPDX-FileCopyrightText: 2022 Ismael Asensio + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick +import QtQuick.Controls as QQC2 + +import org.kde.kirigami as Kirigami +import org.kde.plasma.plasmoid +import org.kde.plasma.private.weather +import org.kde.kcmutils as KCM + +KCM.SimpleKCM { + readonly property bool canShowMoreInCompactMode: !Plasmoid.needsToBeSquare + + property bool cfg_showTemperatureInCompactMode + property bool cfg_showTemperatureInBadge + + property alias cfg_showTemperatureInTooltip: showTemperatureInTooltipCheckBox.checked + property alias cfg_showWindInTooltip: showWindInTooltipCheckBox.checked + property alias cfg_showPressureInTooltip: showPressureInTooltipCheckBox.checked + property alias cfg_showHumidityInTooltip: showHumidityInTooltipCheckBox.checked + + function setShowTemperature(inCompactMode, inBadge) { + cfg_showTemperatureInCompactMode = inCompactMode + cfg_showTemperatureInBadge = inBadge + } + + Kirigami.FormLayout { + Item { + Kirigami.FormData.isSection: true + Kirigami.FormData.label: i18nc("@title:group", "Compact Mode") + + // TODO: conditionalize this once there are also settings for non-compact mode + visible: false + } + + QQC2.RadioButton { + id: radioTempInBadge + Kirigami.FormData.label: i18nc("@label", "Show temperature:") + checked: cfg_showTemperatureInCompactMode && (cfg_showTemperatureInBadge || !canShowMoreInCompactMode) + onToggled: setShowTemperature(true, true) + text: i18nc("@option:radio Show temperature:", "Over the widget icon") + } + + QQC2.RadioButton { + id: radioTempBesideIcon + visible: canShowMoreInCompactMode + checked: cfg_showTemperatureInCompactMode && !cfg_showTemperatureInBadge && canShowMoreInCompactMode + onToggled: setShowTemperature(true, false) + text: i18nc("@option:radio Show temperature:", "Beside the widget icon") + } + + QQC2.RadioButton { + id: radioTempHide + checked: !cfg_showTemperatureInCompactMode + onToggled: setShowTemperature(false, false) + text: i18nc("@option:radio Show temperature:", "Do not show") + } + + Item { + Kirigami.FormData.isSection: true + } + + QQC2.CheckBox { + id: showTemperatureInTooltipCheckBox + Kirigami.FormData.label: i18nc("@label", "Show in tooltip:") + text: i18nc("@option:check", "Temperature") + } + + QQC2.CheckBox { + id: showWindInTooltipCheckBox + text: i18nc("@option:check Show in tooltip: wind", "Wind") + } + + QQC2.CheckBox { + id: showPressureInTooltipCheckBox + text: i18nc("@option:check Show in tooltip: pressure", "Pressure") + } + + QQC2.CheckBox { + id: showHumidityInTooltipCheckBox + text: i18nc("@option:check Show in tooltip: humidity", "Humidity") + } + } +} diff --git a/applets/weather/package/contents/ui/config/ConfigUnits.qml b/applets/weather/package/contents/ui/config/ConfigUnits.qml new file mode 100644 index 00000000..98fdf512 --- /dev/null +++ b/applets/weather/package/contents/ui/config/ConfigUnits.qml @@ -0,0 +1,54 @@ +/* + * SPDX-FileCopyrightText: 2016 Friedrich W. H. Kossebau + * SPDX-FileCopyrightText: 2022 Ismael Asensio + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick +import QtQuick.Controls as QQC2 + +import org.kde.kirigami as Kirigami +import org.kde.plasma.private.weather +import org.kde.kcmutils as KCM + +KCM.SimpleKCM { + property alias cfg_temperatureUnit: temperatureComboBox.unit + property alias cfg_pressureUnit: pressureComboBox.unit + property alias cfg_speedUnit: windSpeedComboBox.unit + property alias cfg_visibilityUnit: visibilityComboBox.unit + + Kirigami.FormLayout { + component UnitSelector: QQC2.ComboBox { + property int unit + + textRole: "display" + currentIndex: model.listIndexForUnitId(unit) + onActivated: unit = model.unitIdForListIndex(currentIndex) + } + + UnitSelector { + id: temperatureComboBox + Kirigami.FormData.label: i18nc("@label:listbox", "Temperature:") + model: TemperatureUnitListModel + } + + UnitSelector { + id: pressureComboBox + Kirigami.FormData.label: i18nc("@label:listbox", "Pressure:") + model: PressureUnitListModel + } + + UnitSelector { + id: windSpeedComboBox + Kirigami.FormData.label: i18nc("@label:listbox", "Wind speed:") + model: WindSpeedUnitListModel + } + + UnitSelector { + id: visibilityComboBox + Kirigami.FormData.label: i18nc("@label:listbox", "Visibility:") + model: VisibilityUnitListModel + } + } +} diff --git a/applets/weather/package/contents/ui/config/ConfigWeatherStation.qml b/applets/weather/package/contents/ui/config/ConfigWeatherStation.qml new file mode 100644 index 00000000..bc45ad9a --- /dev/null +++ b/applets/weather/package/contents/ui/config/ConfigWeatherStation.qml @@ -0,0 +1,202 @@ +/* + * SPDX-FileCopyrightText: 2016, 2018 Friedrich W. H. Kossebau + * SPDX-FileCopyrightText: 2022 Ismael Asensio + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick +import QtQuick.Controls as QQC2 +import QtQuick.Layouts + +import org.kde.kirigami as Kirigami +import org.kde.plasma.plasmoid +import org.kde.plasma.private.weather +import org.kde.kcmutils as KCM + +KCM.ScrollViewKCM { + id: weatherStationConfigPage + + property string cfg_source + property alias cfg_updateInterval: updateIntervalSpin.value + + readonly property var providers: Plasmoid.providers + + readonly property var sourceDetails: cfg_source ? cfg_source.split('|') : "" + readonly property bool hasSource: sourceDetails.length > 2 + readonly property bool canSearch: !!searchStringEdit.text && Object.keys(providers).length + + // The model property `isValidatingInput` doesn't account for the timer delay + // We use a custom property to provide a more responsive feedback + property bool isSearching: false + + LocationListModel { + id: locationListModel + onLocationSearchDone: { + isSearching = false; + } + } + + header: ColumnLayout { + + spacing: Kirigami.Units.smallSpacing + + Kirigami.FormLayout { + id: formLayout + + QQC2.SpinBox { + id: updateIntervalSpin + + Kirigami.FormData.label: i18nc("@label:spinbox", "Update every:") + + textFromValue: function(value) { + return (i18np("%1 minute", "%1 minutes", value)); + } + valueFromText: function(text) { + return parseInt(text); + } + + from: 30 + to: 3600 + editable: true + } + + QQC2.Label { + Kirigami.FormData.label: i18nc("@label", "Location:") + + Layout.fillWidth: true + elide: Text.ElideRight + opacity: hasSource ? 1 : 0.7 + + text: hasSource ? sourceDetails[2] : i18nc("No location is currently selected", "None selected") + textFormat: Text.PlainText + } + + QQC2.Label { + Kirigami.FormData.label: hasSource ? i18nc("@label", "Provider:") : "" + + Layout.fillWidth: true + elide: Text.ElideRight + // Keep it visible to avoid height changes which can confuse AppletConfigurationPage + opacity: hasSource ? 1 : 0 + + text: hasSource ? providers[sourceDetails[0]] : "" + textFormat: Text.PlainText + } + + Item { + // This dummy item adds some spacing and makes the form layout less jumpy + Kirigami.FormData.isSection: true + implicitWidth: Math.min(Kirigami.Units.gridUnit * 15, weatherStationConfigPage.width - Kirigami.Units.gridUnit) + } + } + + Kirigami.SearchField { + id: searchStringEdit + + Layout.fillWidth: true + + focus: true + enabled: Object.keys(providers).length > 0 + placeholderText: hasSource ? i18nc("@info:placeholder", "Enter new location") : i18nc("@info:placeholder", "Enter location") + + Timer { + id: searchDelayTimer + interval: 500 + onTriggered: { + if (!canSearch) { + locationListModel.clear(); + return; + } + locationListModel.searchLocations(searchStringEdit.text, Object.keys(providers)); + } + } + + onTextChanged: { + isSearching = text.length > 0 + searchDelayTimer.restart(); + } + + Keys.onUpPressed: event => { + if (locationListView.currentIndex != 0) { + locationListView.currentIndex--; + } + event.accepted = true; + } + Keys.onDownPressed: event => { + if (locationListView.currentIndex != locationListView.count - 1) { + locationListView.currentIndex++; + } + event.accepted = true; + } + Keys.onEscapePressed: event => { + if (searchStringEdit.text.length === 0) { + event.accepted = false; + return; + } + searchStringEdit.clear(); + event.accepted = true; + } + } + } + + view: ListView { + id: locationListView + model: locationListModel + focus: true + activeFocusOnTab: true + keyNavigationEnabled: true + enabled: Object.keys(providers).length > 0 + + onCurrentIndexChanged: { + const source = locationListModel.valueForListIndex(locationListView.currentIndex); + if (source) { + weatherStationConfigPage.cfg_source = source; + } + } + + delegate: QQC2.ItemDelegate { + width: locationListView.width + text: model.display + highlighted: ListView.isCurrentItem + + onClicked: { + locationListView.forceActiveFocus(); + locationListView.currentIndex = index; + } + } + + // To avoid start with a highlighted item on the next search + onCountChanged: { + if (count === 0) { + currentIndex = -1; + } + } + + Keys.forwardTo: searchStringEdit + + Kirigami.PlaceholderMessage { + id: listViewPlaceholder + anchors.centerIn: parent + width: parent.width - (Kirigami.Units.largeSpacing * 4) + visible: locationListView.count === 0 && !isSearching + text: { + if (canSearch) { // There is a search text + return i18nc("@info", "No weather stations found for '%1'", searchStringEdit.text); + } else if (hasSource) { + return i18nc("@info", "Search for a weather station to change your location"); + } else { + return i18nc("@info", "Search for a weather station to set your location"); + } + } + explanation: canSearch ? i18nc("@info:usagetip", "If you've used this weather station in the past, it's possible that a server outage at the weather station provider has made it temporarily unavailable. Try again later.") : "" + + } + + QQC2.BusyIndicator { + id: busy + anchors.centerIn: parent + visible: locationListView.count === 0 && isSearching + } + } +} diff --git a/applets/weather/package/contents/ui/main.qml b/applets/weather/package/contents/ui/main.qml new file mode 100644 index 00000000..ff587952 --- /dev/null +++ b/applets/weather/package/contents/ui/main.qml @@ -0,0 +1,435 @@ +/* + * SPDX-FileCopyrightText: 2018 Friedrich W. H. Kossebau + * SPDX-FileCopyrightText: 2023 Ismael Asensio + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick + +import org.kde.plasma.plasmoid +import org.kde.plasma.core as PlasmaCore +import org.kde.plasma.plasma5support as P5Support + +import org.kde.plasma.private.weather + +PlasmoidItem { + id: root + + Plasmoid.backgroundHints: PlasmaCore.Types.DefaultBackground | PlasmaCore.Types.ConfigurableBackground + + readonly property bool inPanel: [ + PlasmaCore.Types.TopEdge, + PlasmaCore.Types.RightEdge, + PlasmaCore.Types.BottomEdge, + PlasmaCore.Types.LeftEdge, + ].includes(Plasmoid.location) + + readonly property string weatherSource: Plasmoid.configuration.source + readonly property int updateInterval: Plasmoid.configuration.updateInterval + readonly property int displayTemperatureUnit: Plasmoid.configuration.temperatureUnit + readonly property int displaySpeedUnit: Plasmoid.configuration.speedUnit + readonly property int displayPressureUnit: Plasmoid.configuration.pressureUnit + readonly property int displayVisibilityUnit: Plasmoid.configuration.visibilityUnit + + property int status: Util.Normal + + readonly property int invalidUnit: -1 //TODO: make KUnitConversion::InvalidUnit usable here + + // model providing final display strings for observation properties + readonly property var observationModel: { + const model = {}; + const data = weatherDataSource.currentData || {}; + + function getNumber(key) { + const number = data[key]; + if (typeof number === "string") { + const parsedNumber = parseFloat(number); + return isNaN(parsedNumber) ? null : parsedNumber; + } + return (typeof number !== "undefined") && (number !== "") ? number : null; + } + function getNumberOrString(key) { + const number = data[key]; + return (typeof number !== "undefined") && (number !== "") ? number : null; + } + + const reportTemperatureUnit = data["Temperature Unit"] || invalidUnit; + const reportPressureUnit = data["Pressure Unit"] || invalidUnit; + const reportVisibilityUnit = data["Visibility Unit"] || invalidUnit; + const reportWindSpeedUnit = data["Wind Speed Unit"] || invalidUnit; + + model["conditions"] = data["Current Conditions"] || ""; + + const conditionIconName = data["Condition Icon"] || null; + model["conditionIconName"] = conditionIconName ? Util.existingWeatherIconName(conditionIconName) : "weather-none-available"; + + const temperature = getNumber("Temperature"); + model["temperature"] = temperature !== null ? Util.temperatureToDisplayString(displayTemperatureUnit, temperature, reportTemperatureUnit, true, false) : ""; + + + // "Feels like" temperature indices. They are mutually exclusive as + // they're used by different agencies or on different range of temperatures + for (const indexName of ["Windchill", "Humidex", "Heat Index"]) { + const value = getNumber(indexName); + if (value !== null) { + model["feelsLikeIndex"] = indexName; + model["feelsLikeTemperature"] = + Util.temperatureToDisplayString(displayTemperatureUnit, value, reportTemperatureUnit, true, false); + break; + } + } + + const dewpoint = getNumber("Dewpoint"); + model["dewpoint"] = dewpoint !== null ? + Util.temperatureToDisplayString(displayTemperatureUnit, dewpoint, reportTemperatureUnit) : ""; + + const pressure = getNumber("Pressure"); + model["pressure"] = pressure !== null ? + Util.valueToDisplayString(displayPressureUnit, pressure, reportPressureUnit, 2) : ""; + + const pressureTendency = (data && data["Pressure Tendency"]) || null; + model["pressureTendency"] = + pressureTendency === "rising" ? i18nc("pressure tendency", "Rising") : + pressureTendency === "falling" ? i18nc("pressure tendency", "Falling") : + pressureTendency === "steady" ? i18nc("pressure tendency", "Steady") : + /* else */ ""; + + const visibility = getNumberOrString("Visibility"); + model["visibility"] = visibility !== null ? + ((reportVisibilityUnit !== invalidUnit) ? + Util.valueToDisplayString(displayVisibilityUnit, visibility, reportVisibilityUnit, 1) : visibility) : + ""; + + const humidity = getNumber("Humidity"); + model["humidity"] = humidity !== null ? Util.percentToDisplayString(humidity) : ""; + + // TODO: missing check for windDirection validness + const windDirection = data["Wind Direction"] || ""; + const windSpeed = getNumberOrString("Wind Speed"); + let windSpeedText; + if (windSpeed !== null && windSpeed !== "") { + const windSpeedNumeric = (typeof windSpeed !== 'number') ? parseFloat(windSpeed) : windSpeed; + if (!isNaN(windSpeedNumeric)) { + if (windSpeedNumeric !== 0) { + windSpeedText = Util.valueToDisplayString(displaySpeedUnit, windSpeedNumeric, reportWindSpeedUnit, 1); + } else { + windSpeedText = i18nc("Wind condition", "Calm"); + } + } else { + // TODO: i18n? + windSpeedText = windSpeed; + } + } + model["windSpeed"] = windSpeedText || ""; + model["windDirectionId"] = windDirection; + model["windDirection"] = windDirection ? i18nc("wind direction", windDirection) : ""; + + const windGust = getNumber("Wind Gust"); + model["windGust"] = windGust !== null ? Util.valueToDisplayString(displaySpeedUnit, windGust, reportWindSpeedUnit, 1) : ""; + + return model; + } + + readonly property var generalModel: { + const model = {}; + const data = weatherDataSource.currentData || {}; + + const todayForecastTokens = (data["Short Forecast Day 0"] || "").split("|"); + + model["location"] = data["Place"] || ""; + model["courtesy"] = data["Credit"] || ""; + model["creditUrl"] = data["Credit Url"] || ""; + + let forecastDayCount = parseInt(data["Total Weather Days"] || ""); + + // Some providers use a 12h forecast, with day and night distinct info + const hasNightForecasts = weatherSource && forecastDayCount > 8; + model["forecastNightRow"] = hasNightForecasts; + if (hasNightForecasts) { + model["forecastStartsAtNight"] = data["Forecast Starts at Night"] || false; + forecastDayCount = Math.ceil(forecastDayCount / 2); + } + + const forecastTitle = (!isNaN(forecastDayCount) && forecastDayCount > 0) ? + i18ncp("Forecast period timeframe", "1 Day", "%1 Days", forecastDayCount) : "" + model["forecastTitle"] = forecastTitle; + + let conditionIconName = observationModel.conditionIconName; + if (!conditionIconName || conditionIconName === "weather-none-available") { + // try icon from current weather forecast + if (todayForecastTokens.length === 6 && todayForecastTokens[1] !== "N/U") { + conditionIconName = Util.existingWeatherIconName(todayForecastTokens[1]); + } else { + conditionIconName = "weather-none-available"; + } + } + model["currentConditionIconName"] = conditionIconName; + + return model; + } + + readonly property var detailsModel: { + const model = []; + + if (observationModel.dewpoint) { + model.push({ + "label": i18nc("@label ground temperature", "Dewpoint:"), + "text": observationModel.dewpoint + }); + } + + if (observationModel.pressure) { + model.push({ + "label": i18nc("@label", "Pressure:"), + "text": observationModel.pressure + }); + } + + if (observationModel.pressureTendency) { + model.push({ + "label": i18nc("@label pressure tendency, rising/falling/steady", "Pressure Tendency:"), + "text": observationModel.pressureTendency + }); + } + + if (observationModel.visibility) { + model.push({ + "label": i18nc("@label", "Visibility:"), + "text": observationModel.visibility + }); + } + + if (observationModel.humidity) { + model.push({ + "label": i18nc("@label", "Humidity:"), + "text": observationModel.humidity + }); + } + + if (observationModel.windGust) { + model.push({ + "label": i18nc("@label", "Wind Gust:"), + "text": observationModel.windGust + }); + } + + return model; + } + + readonly property var forecastModel: { + const model = []; + const data = weatherDataSource.currentData; + + const forecastDayCount = parseInt((data && data["Total Weather Days"]) || ""); + if (isNaN(forecastDayCount) || forecastDayCount <= 0) { + return model; + } + + const reportTemperatureUnit = (data && data["Temperature Unit"]) || invalidUnit; + + for (let i = 0; i < forecastDayCount; ++i) { + const forecastInfo = { + period: "", + icon: "", + condition: "", + probability: "", + tempHigh: "", + tempLow: "", + } + + const forecastDayKey = "Short Forecast Day " + i; + const forecastDayTokens = ((data && data[forecastDayKey]) || "").split("|"); + if (forecastDayTokens.length !== 6) { + // We don't have the right number of tokens, abort trying + continue; + } + + // If the first item is a night forecast add an empty item to reserve the space in the grid + if (i === 0 && generalModel.forecastNightRow && generalModel.forecastStartsAtNight) { + model.push(null) + } + + forecastInfo["period"] = forecastDayTokens[0]; + + // If we see N/U (Not Used) we skip the item + const weatherIconName = forecastDayTokens[1]; + if (weatherIconName && weatherIconName !== "N/U") { + forecastInfo["icon"] = Util.existingWeatherIconName(weatherIconName); + forecastInfo["condition"] = forecastDayTokens[2]; + + const probability = forecastDayTokens[5]; + if (probability !== "N/U" && probability !== "N/A" && probability > 7.5) { + forecastInfo["probability"] = Math.round(probability / 5 ) * 5; + } + } + + const tempHigh = forecastDayTokens[3]; + if (tempHigh !== "N/U" && tempHigh !== "N/A" && tempHigh) { + forecastInfo["tempHigh"] = Util.temperatureToDisplayString(displayTemperatureUnit, tempHigh, reportTemperatureUnit, true); + } + + const tempLow = forecastDayTokens[4]; + if (tempLow !== "N/U" && tempLow !== "N/A" && tempLow) { + forecastInfo["tempLow"] = Util.temperatureToDisplayString(displayTemperatureUnit, tempLow, reportTemperatureUnit, true); + } + + model.push(forecastInfo); + } + + return model; + } + + readonly property var noticesModel: { + const model = []; + const data = weatherDataSource.currentData; + + let warningsCount = parseInt((data && data["Total Warnings Issued"]) || ""); + if (isNaN(warningsCount)) { + warningsCount = 0; + } + for (let i = 0; i < warningsCount; ++i) { + model.push({ + 'description': data[`Warning Description ${i}`], + 'infoUrl': data[`Warning Info ${i}`], + 'timestamp': data[`Warning Timestamp ${i}`], + 'priority': data[`Warning Priority ${i}`] ?? 0, + }); + } + + return model; + } + + function symbolicizeIconName(iconName) { + const symbolicSuffix = "-symbolic"; + if (iconName.endsWith(symbolicSuffix)) { + return iconName; + } + + return iconName + symbolicSuffix; + } + + P5Support.DataSource { + id: weatherDataSource + + readonly property var currentData: data[weatherSource] + + engine: "weather" + connectedSources: weatherSource + interval: updateInterval * 60 * 1000 + onConnectedSourcesChanged: { + if (weatherSource) { + status = Util.Connecting + connectionTimeoutTimer.start(); + } + } + onCurrentDataChanged: { + if (currentData) { + status = Util.Normal + connectionTimeoutTimer.stop(); + } + } + } + + Timer { + id: connectionTimeoutTimer + + interval: 60 * 1000 // 1 min + repeat: false + onTriggered: { + status = Util.Timeout; + } + } + + Plasmoid.icon: { + let iconName; + // workaround for now to ensure "Please configure" tooltip + // TODO: remove when configurationRequired works + if (status === Util.NeedsConfiguration) { + iconName = "configure"; + } else { + iconName = generalModel.currentConditionIconName; + } + + if (inPanel) { + iconName = symbolicizeIconName(iconName); + } + + return iconName; + } + Plasmoid.busy: status === Util.Connecting + Plasmoid.configurationRequired: status === Util.NeedsConfiguration + + toolTipMainText: (status === Util.NeedsConfiguration) ? + i18nc("@info:tooltip %1 is the translated plasmoid name", "Click to configure %1", Plasmoid.title) : + generalModel.location + + toolTipSubText: { + if (!generalModel.location) { + return ""; + } + const tooltips = []; + const temperature = Plasmoid.configuration.showTemperatureInTooltip ? observationModel.temperature : null; + if (observationModel.conditions && temperature) { + tooltips.push(i18nc("weather condition + temperature", + "%1 %2", observationModel.conditions, temperature)); + } else if (observationModel.conditions || temperature) { + tooltips.push(observationModel.conditions || temperature); + } + if (Plasmoid.configuration.showWindInTooltip && observationModel.windSpeed) { + if (observationModel.windDirection) { + if (observationModel.windGust) { + tooltips.push(i18nc("winddirection windspeed (windgust)", "%1 %2 (%3)", + observationModel.windDirection, observationModel.windSpeed, observationModel.windGust)); + } else { + tooltips.push(i18nc("winddirection windspeed", "%1 %2", + observationModel.windDirection, observationModel.windSpeed)); + } + } else { + tooltips.push(observationModel.windSpeed); + } + } + if (Plasmoid.configuration.showPressureInTooltip && observationModel.pressure) { + if (observationModel.pressureTendency) { + tooltips.push(i18nc("pressure (tendency)", "%1 (%2)", + observationModel.pressure, observationModel.pressureTendency)); + } else { + tooltips.push(observationModel.pressure); + } + } + if (Plasmoid.configuration.showHumidityInTooltip && observationModel.humidity) { + tooltips.push(i18n("Humidity: %1", observationModel.humidity)); + } + + return tooltips.join("\n"); + } + + // Only exists because the default CompactRepresentation doesn't expose: + // - Icon overlays, or a generic way to overlay something on top of the icon + // - The ability to show text below or beside the icon + // TODO remove once it gains those features. + compactRepresentation: CompactRepresentation { + generalModel: root.generalModel + observationModel: root.observationModel + } + + fullRepresentation: FullRepresentation { + generalModel: root.generalModel + observationModel: root.observationModel + } + + Binding { + target: Plasmoid + property: "needsToBeSquare" + value: (Plasmoid.containmentType & PlasmaCore.Types.CustomEmbeddedContainment) + | (Plasmoid.containmentDisplayHints & PlasmaCore.Types.ContainmentForcesSquarePlasmoids) + } + + onWeatherSourceChanged: { + if (weatherSource.length === 0) { + status = Util.NeedsConfiguration + } + } + + Component.onCompleted: weatherSourceChanged() +} diff --git a/applets/weather/package/metadata.json b/applets/weather/package/metadata.json new file mode 100644 index 00000000..a13381c8 --- /dev/null +++ b/applets/weather/package/metadata.json @@ -0,0 +1,147 @@ +{ + "KPackageStructure": "Plasma/Applet", + "KPlugin": { + "Authors": [ + { + "Email": "lampih@gmail.com", + "Name": "Luís Gabriel Lima", + "Name[ar]": "لويس غابرييل ليما", + "Name[az]": "Luís Gabriel Lima", + "Name[bg]": "Luís Gabriel Lima", + "Name[ca@valencia]": "Luís Gabriel Lima", + "Name[ca]": "Luís Gabriel Lima", + "Name[cs]": "Luís Gabriel Lima", + "Name[da]": "Luís Gabriel Lima", + "Name[de]": "Luís Gabriel Lima", + "Name[en_GB]": "Luís Gabriel Lima", + "Name[eo]": "Luís Gabriel Lima", + "Name[es]": "Luís Gabriel Lima", + "Name[eu]": "Luís Gabriel Lima", + "Name[fi]": "Luís Gabriel Lima", + "Name[fr]": "Luís Gabriel Lima", + "Name[gl]": "Luís Gabriel Lima", + "Name[he]": "לואיס גבריאל לימה", + "Name[hu]": "Luís Gabriel Lima", + "Name[ia]": "Luís Gabriel Lima", + "Name[id]": "Luís Gabriel Lima", + "Name[is]": "Luís Gabriel Lima", + "Name[it]": "Luís Gabriel Lima", + "Name[ja]": "Luís Gabriel Lima", + "Name[ka]": "Luís Gabriel Lima", + "Name[ko]": "Luís Gabriel Lima", + "Name[lt]": "Luís Gabriel Lima", + "Name[lv]": "Luís Gabriel Lima", + "Name[nl]": "Luís Gabriel Lima", + "Name[nn]": "Luís Gabriel Lima", + "Name[pl]": "Luís Gabriel Lima", + "Name[pt]": "Luís Gabriel Lima", + "Name[pt_BR]": "Luís Gabriel Lima", + "Name[ro]": "Luís Gabriel Lima", + "Name[ru]": "Luís Gabriel Lima", + "Name[sk]": "Luís Gabriel Lima", + "Name[sl]": "Luís Gabriel Lima", + "Name[sv]": "Luís Gabriel Lima", + "Name[tr]": "Luís Gabriel Lima", + "Name[uk]": "Luís Gabriel Lima", + "Name[vi]": "Luís Gabriel Lima", + "Name[x-test]": "xxLuís Gabriel Limaxx", + "Name[zh_CN]": "Luís Gabriel Lima", + "Name[zh_TW]": "Luís Gabriel Lima" + } + ], + "BugReportUrl": "https://bugs.kde.org/enter_bug.cgi?product=kdeplasma-addons&component=Weather", + "Category": "Environment and Weather", + "Description": "Forecast and current conditions", + "Description[ar]": "حالة الطق الحالية و المتوقعة", + "Description[az]": "Cari və gözlənilən hava məlumatı", + "Description[bg]": "Прогноза за времето и настоящи условия", + "Description[ca@valencia]": "Previsió i condicions actuals", + "Description[ca]": "Previsió i condicions actuals", + "Description[cs]": "Předpověď a současná situace", + "Description[da]": "Prognose og nuværende forhold", + "Description[de]": "Vorhersage und aktuelle Zustand", + "Description[en_GB]": "Forecast and current conditions", + "Description[eo]": "Prognozo kaj aktualaj kondiĉoj", + "Description[es]": "Previsión meteorológica y condiciones actuales", + "Description[eu]": "Iragarpena eta uneko egoera", + "Description[fi]": "Ennuste ja nykyinen säätila", + "Description[fr]": "Prévisions et conditions actuelles", + "Description[gl]": "Prognóstico e condicións actuais", + "Description[he]": "תשקיף ותנאים נוכחיים", + "Description[hu]": "Előrejelzés és jelenlegi helyzet", + "Description[ia]": "Prevision e conditiones currente", + "Description[id]": "Prakiraan dan kondisi saat ini", + "Description[is]": "Veðurspá og veðurlýsing", + "Description[it]": "Previsioni e condizioni attuali", + "Description[ja]": "現在の気象状況と予報", + "Description[ka]": "პროგნოზი და მიმდინარე მდგობარეობა", + "Description[ko]": "일기 예보와 현재 날씨", + "Description[lt]": "Orų prognozė ir dabartinės orų sąlygos", + "Description[lv]": "Prognozes un pašreizējie apstākļi", + "Description[nl]": "Voorspelling en huidige condities", + "Description[nn]": "Vêrmelding og gjeldande vêr", + "Description[pl]": "Prognoza i obecny warunki pogodowe", + "Description[pt]": "Previsão e condições actuais", + "Description[pt_BR]": "Previsão e condições atuais", + "Description[ro]": "Prognoza și condițiile actuale", + "Description[ru]": "Прогноз и информация о погоде в данный момент", + "Description[sk]": "Predpoveď a aktuálne podmienky", + "Description[sl]": "Napoved in trenutne razmere", + "Description[sv]": "Prognos och aktuella förhållanden", + "Description[tr]": "Hava tahmini ve geçerli durum", + "Description[uk]": "Прогноз і поточні умови", + "Description[vi]": "Dự báo và điều kiện hiện tại", + "Description[x-test]": "xxForecast and current conditionsxx", + "Description[zh_CN]": "天气预告和当前天气", + "Description[zh_TW]": "預報及目前氣象觀測", + "Icon": "weather-clear", + "Id": "org.kde.plasma.weather", + "License": "GPL-2.0+", + "Name": "Weather Report", + "Name[ar]": "تقرير الطقس", + "Name[az]": "Hava məlumatı", + "Name[bg]": "Прогноза за времето", + "Name[ca@valencia]": "Informe meteorològic", + "Name[ca]": "Informe meteorològic", + "Name[cs]": "Hlášení počasí", + "Name[da]": "Vejrrapport", + "Name[de]": "Wetterbericht", + "Name[en_GB]": "Weather Report", + "Name[eo]": "Vetera Raporto", + "Name[es]": "Informe meteorológico", + "Name[eu]": "Eguraldi iragarpena", + "Name[fi]": "Säätiedotus", + "Name[fr]": "Bulletin météo", + "Name[gl]": "Informe meteorolóxico", + "Name[he]": "דוח מזג אוויר", + "Name[hu]": "Időjárásjelentés", + "Name[ia]": "Reporto de Tempore meteorologic", + "Name[id]": "Laporan Cuaca", + "Name[is]": "Veðurfregnir", + "Name[it]": "Previsioni meteo", + "Name[ja]": "天気予報", + "Name[ka]": "ამინდის ანგარიში", + "Name[ko]": "일기 예보", + "Name[lt]": "Orų pranešimai", + "Name[lv]": "Laikapstākļi", + "Name[nl]": "Weerrapport", + "Name[nn]": "Vêr­melding", + "Name[pl]": "Prognoza pogody", + "Name[pt]": "Boletim Meteorológico", + "Name[pt_BR]": "Previsão do tempo", + "Name[ro]": "Prognoza vremii", + "Name[ru]": "Прогноз погоды", + "Name[sk]": "Hlásenie o počasí", + "Name[sl]": "Vremensko poročilo", + "Name[sv]": "Väderprognos", + "Name[tr]": "Hava Durumu Raporu", + "Name[uk]": "Звіт щодо погоди", + "Name[vi]": "Tin thời tiết", + "Name[x-test]": "xxWeather Reportxx", + "Name[zh_CN]": "天气预报", + "Name[zh_TW]": "天氣預報", + "Website": "https://kde.org/plasma-desktop/" + }, + "X-Plasma-API-Minimum-Version": "6.0", + "X-Plasma-NotificationAreaCategory": "SystemServices" +} diff --git a/applets/weather/plugin/abstractunitlistmodel.cpp b/applets/weather/plugin/abstractunitlistmodel.cpp new file mode 100644 index 00000000..d10a5ebd --- /dev/null +++ b/applets/weather/plugin/abstractunitlistmodel.cpp @@ -0,0 +1,58 @@ +/* + * SPDX-FileCopyrightText: 2016 Friedrich W. H. Kossebau + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "abstractunitlistmodel.h" + +AbstractUnitListModel::AbstractUnitListModel(const QList &items, QObject *parent) + : QAbstractListModel(parent) + , m_items(items) +{ +} + +QVariant AbstractUnitListModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= m_items.size()) { + return QVariant(); + } + + const UnitItem &item = m_items.at(index.row()); + + switch (role) { + case Qt::DisplayRole: + return item.name; + } + + return QVariant(); +} + +int AbstractUnitListModel::rowCount(const QModelIndex &index) const +{ + if (!index.isValid()) { + return m_items.size(); + } + + return 0; +} + +int AbstractUnitListModel::listIndexForUnitId(int unitId) const +{ + for (int i = 0; i < m_items.count(); ++i) { + if (m_items.at(i).unitId == unitId) { + return i; + } + } + + return -1; +} + +int AbstractUnitListModel::unitIdForListIndex(int listIndex) const +{ + if (0 <= listIndex && listIndex < m_items.count()) { + return m_items.at(listIndex).unitId; + } + + return -1; +} diff --git a/applets/weather/plugin/abstractunitlistmodel.h b/applets/weather/plugin/abstractunitlistmodel.h new file mode 100644 index 00000000..df037a68 --- /dev/null +++ b/applets/weather/plugin/abstractunitlistmodel.h @@ -0,0 +1,55 @@ +/* + * SPDX-FileCopyrightText: 2016 Friedrich W. H. Kossebau + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef ABSTRACTUNITLISTMODEL_H +#define ABSTRACTUNITLISTMODEL_H + +#include "util.h" + +#include + +#include +#include + +class UnitItem +{ +public: + UnitItem() + { + } + UnitItem(KUnitConversion::UnitId _unitId) + : name(Util::nameFromUnitId(_unitId)) + , unitId(_unitId) + { + } + + QString name; + KUnitConversion::UnitId unitId; +}; + +Q_DECLARE_METATYPE(UnitItem) +Q_DECLARE_TYPEINFO(UnitItem, Q_MOVABLE_TYPE); + +class AbstractUnitListModel : public QAbstractListModel +{ + Q_OBJECT + +public: + explicit AbstractUnitListModel(const QList &items, QObject *parent = nullptr); + +public: // QAbstractListModel API + QVariant data(const QModelIndex &index, int role) const override; + int rowCount(const QModelIndex &index) const override; + +public: + Q_INVOKABLE int listIndexForUnitId(int unitId) const; + Q_INVOKABLE int unitIdForListIndex(int listIndex) const; + +private: + const QList m_items; +}; + +#endif // ABSTRACTUNITLISTMODEL_H diff --git a/applets/weather/plugin/locationlistmodel.cpp b/applets/weather/plugin/locationlistmodel.cpp new file mode 100644 index 00000000..bba5cbcc --- /dev/null +++ b/applets/weather/plugin/locationlistmodel.cpp @@ -0,0 +1,272 @@ +/* + * SPDX-FileCopyrightText: 2009 Petri Damstén + * SPDX-FileCopyrightText: 2016, 2018 Friedrich W. H. Kossebau + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "locationlistmodel.h" + +#include + +#include + +#include + +WeatherValidator::WeatherValidator(Plasma5Support::DataEngine *weatherDataengine, const QString &ionName, QObject *parent) + : QObject(parent) + , m_weatherDataEngine(weatherDataengine) + , m_ionName(ionName) +{ +} + +WeatherValidator::~WeatherValidator() = default; + +void WeatherValidator::validate(const QString &location) +{ + const QString validationSource = m_ionName + QLatin1String("|validate|") + location; + + m_weatherDataEngine->connectSource(validationSource, this); +} + +void WeatherValidator::dataUpdated(const QString &source, const Plasma5Support::DataEngine::Data &data) +{ + QMap locationSources; + + m_weatherDataEngine->disconnectSource(source, this); + + const auto validationResult = data[QStringLiteral("validate")].toString().split(QLatin1Char('|')); + + if (validationResult.size() < 2) { + Q_EMIT error(i18n("Cannot find '%1' using %2.", source, m_ionName)); + } else if (validationResult[1] == QLatin1String("valid") && validationResult.size() > 2) { + const QString weatherSourcePrefix = validationResult[0] + QLatin1String("|weather|"); + int i = 3; + + const int lastFieldIndex = validationResult.size() - 1; + while (i < lastFieldIndex) { + if (validationResult[i] == QLatin1String("place")) { + const QString &name = validationResult[i + 1]; + QString locationSource; + if (i + 2 < lastFieldIndex && validationResult[i + 2] == QLatin1String("extra")) { + const QString &id = validationResult[i + 3]; + locationSource = weatherSourcePrefix + name + QLatin1Char('|') + id; + i += 4; + } else { + locationSource = weatherSourcePrefix + name; + i += 2; + } + locationSources.insert(name, locationSource); + } else { + ++i; + } + } + } else if (validationResult[1] == QLatin1String("timeout")) { + Q_EMIT error(i18n("Connection to %1 weather server timed out.", m_ionName)); + } + + Q_EMIT finished(locationSources); +} + +LocationItem::LocationItem(const QString &source) + : value(source) +{ + const QStringList sourceTerms = source.split(QLatin1Char('|'), Qt::SkipEmptyParts); + if (sourceTerms.count() <= 2) { + return; + } + weatherService = sourceTerms[0]; + weatherStation = sourceTerms[2]; + quality = relativeQuality(weatherService); +} + +int LocationItem::relativeQuality(const QString &service) const +{ + static const QMap serviceQuality = { + {"wettercom", -1}, // wetter.com does not provide current weather status + {"bbcukmet", 0}, // only 3-day forecast and no alerts + {"dwd", 1}, // 7-day forecast and alerts, but some stations do not provide observation data, + {"noaa", 2}, // 12h 7-day forecast, observation and alerts + {"envcan", 2}, // 12h 7-day forecast, observation and alerts + }; + + if (!serviceQuality.contains(service)) { + return 1; // Fallback. Default quality + } + + return serviceQuality.value(service); +} + +LocationListModel::LocationListModel(QObject *parent) + : QAbstractListModel(parent) + , m_validatingInput(false) + , m_checkedInCount(0) +{ +} + +QVariant LocationListModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= m_locations.size()) { + return QVariant(); + } + + switch (role) { + case Qt::DisplayRole: + return nameForListIndex(index.row()); + } + + return QVariant(); +} + +int LocationListModel::rowCount(const QModelIndex &index) const +{ + if (!index.isValid()) { + return m_locations.size(); + } + + return 0; +} + +bool LocationListModel::isValidatingInput() const +{ + return m_validatingInput; +} + +QString LocationListModel::valueForListIndex(int listIndex) const +{ + if (0 <= listIndex && listIndex < m_locations.count()) { + return m_locations.at(listIndex).value; + } + + return QString(); +} + +QString LocationListModel::nameForListIndex(int listIndex) const +{ + if (0 <= listIndex && listIndex < m_locations.count()) { + const LocationItem &item = m_locations.at(listIndex); + if (!item.weatherService.isEmpty()) { + return i18nc("A weather station location and the weather service it comes from", + "%1 (%2)", + item.weatherStation, + m_serviceCodeToDisplayName.value(item.weatherService, item.weatherService)); + } + } + + return QString(); +} + +void LocationListModel::searchLocations(const QString &searchString, const QStringList &services) +{ + m_checkedInCount = 0; + + // reset current validators + qDeleteAll(m_validators); + m_validators.clear(); + + m_searchString = searchString; + + if (!m_validatingInput) { + m_validatingInput = true; + Q_EMIT validatingInputChanged(true); + } + + beginResetModel(); + m_locations.clear(); + endResetModel(); + + if (searchString.isEmpty()) { + completeSearch(); + return; + } + + Plasma5Support::DataEngine *dataengine = dataEngine(QStringLiteral("weather")); + + const QVariantList plugins = dataengine->containerForSource(QStringLiteral("ions"))->data().values(); + for (const QVariant &plugin : plugins) { + const QStringList pluginInfo = plugin.toString().split(QLatin1Char('|')); + if (pluginInfo.count() > 1) { + const QString &ionId = pluginInfo[1]; + if (!services.contains(ionId)) { + continue; + } + + m_serviceCodeToDisplayName[pluginInfo[1]] = pluginInfo[0]; + + // qDebug() << "ion: " << pluginInfo[0] << pluginInfo[1]; + // d->ions.insert(pluginInfo[1], pluginInfo[0]); + + auto *validator = new WeatherValidator(dataengine, ionId, this); + connect(validator, &WeatherValidator::error, this, &LocationListModel::validatorError); + connect(validator, &WeatherValidator::finished, this, &LocationListModel::addSources); + + m_validators.append(validator); + } + } + + for (auto *validator : std::as_const(m_validators)) { + validator->validate(m_searchString); + } +} + +void LocationListModel::validatorError(const QString &error) +{ + qDebug() << error; +} + +void LocationListModel::addSources(const QMap &sources) +{ + static QList fallbackLocations; + + // TODO: be more elaborate and use beginInsertRows() & endInsertRows() + beginResetModel(); + + for (const auto &source : sources) { + const auto item = LocationItem(source); + if (item.weatherStation.isEmpty()) { + continue; + } + if (item.quality >= 0) { + m_locations << item; + } else { + fallbackLocations << item; + } + } + + // Promote services with better quality to the top of the list + std::stable_sort(m_locations.begin(), m_locations.end(), [](const auto &a, const auto &b) { + return a.quality >= b.quality; + }); + + endResetModel(); + + ++m_checkedInCount; + if (m_checkedInCount >= m_validators.count()) { + if (m_locations.isEmpty()) { + beginResetModel(); + m_locations = fallbackLocations; + endResetModel(); + } + fallbackLocations.clear(); + completeSearch(); + } +} + +void LocationListModel::completeSearch() +{ + m_validatingInput = false; + const bool success = !m_locations.empty(); + Q_EMIT locationSearchDone(success, m_searchString); + Q_EMIT validatingInputChanged(false); +} + +void LocationListModel::clear() +{ + beginResetModel(); + m_locations.clear(); + endResetModel(); + + m_checkedInCount = 0; + m_validatingInput = false; + Q_EMIT validatingInputChanged(false); +} diff --git a/applets/weather/plugin/locationlistmodel.h b/applets/weather/plugin/locationlistmodel.h new file mode 100644 index 00000000..854e6e17 --- /dev/null +++ b/applets/weather/plugin/locationlistmodel.h @@ -0,0 +1,106 @@ +/* + * SPDX-FileCopyrightText: 2016 Friedrich W. H. Kossebau + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef LOCATIONLISTMODEL_H +#define LOCATIONLISTMODEL_H + +#include +#include + +#include +#include +#include + +class WeatherValidator : public QObject +{ + Q_OBJECT +public: + WeatherValidator(Plasma5Support::DataEngine *weatherDataengine, const QString &ionName, QObject *parent = nullptr); + ~WeatherValidator() override; + + /** + * @param location the name of the location to find + */ + void validate(const QString &location); + +Q_SIGNALS: + /** + * Emitted when an error in validation occurs + **/ + void error(const QString &message); + + /** + * Emitted when validation is done + * @param sources a mapping of user-friendly names to the DataEngine source + **/ + void finished(const QMap &sources); + +public Q_SLOTS: // callback for the weather dataengine + void dataUpdated(const QString &source, const Plasma5Support::DataEngine::Data &data); + +private: + Plasma5Support::DataEngine *m_weatherDataEngine; + QString m_ionName; +}; + +class LocationItem +{ +public: + LocationItem(const QString &source); + + int relativeQuality(const QString &service) const; + + QString weatherStation; + QString weatherService; + QString value; + int quality = 0; +}; + +Q_DECLARE_METATYPE(LocationItem) +Q_DECLARE_TYPEINFO(LocationItem, Q_MOVABLE_TYPE); + +class LocationListModel : public QAbstractListModel, public Plasma5Support::DataEngineConsumer +{ + Q_OBJECT + Q_PROPERTY(bool validatingInput READ isValidatingInput NOTIFY validatingInputChanged) + +public: + explicit LocationListModel(QObject *parent = nullptr); + +public: // QAbstractListModel API + QVariant data(const QModelIndex &index, int role) const override; + int rowCount(const QModelIndex &index) const override; + +public: + bool isValidatingInput() const; + +public: + Q_INVOKABLE QString nameForListIndex(int listIndex) const; + Q_INVOKABLE QString valueForListIndex(int listIndex) const; + Q_INVOKABLE void searchLocations(const QString &searchString, const QStringList &services); + Q_INVOKABLE void clear(); + +Q_SIGNALS: + void validatingInputChanged(bool validatingInput); + void locationSearchDone(bool success, const QString &searchString); + +private: + void addSources(const QMap &sources); + void validatorError(const QString &error); + void completeSearch(); + +private: + QList m_locations; + + bool m_validatingInput; + QString m_searchString; + int m_checkedInCount; + QList m_validators; + + QMap m_serviceCodeToDisplayName; +}; + +#endif // LOCATIONLISTMODEL_H diff --git a/applets/weather/plugin/plugin.cpp b/applets/weather/plugin/plugin.cpp new file mode 100644 index 00000000..a446cfb9 --- /dev/null +++ b/applets/weather/plugin/plugin.cpp @@ -0,0 +1,78 @@ +/* + * SPDX-FileCopyrightText: 2016 Friedrich W. H. Kossebau + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "abstractunitlistmodel.h" +#include "locationlistmodel.h" +#include "util.h" + +#include +#include + +static QObject *temperatureUnitListModelSingletonTypeProvider(QQmlEngine *engine, QJSEngine * /*scriptEngine*/) +{ + QList items{UnitItem(KUnitConversion::Celsius), UnitItem(KUnitConversion::Fahrenheit), UnitItem(KUnitConversion::Kelvin)}; + return new AbstractUnitListModel(items, engine); +} + +static QObject *pressureUnitListModelSingletonTypeProvider(QQmlEngine *engine, QJSEngine * /*scriptEngine*/) +{ + const QList items{ + UnitItem(KUnitConversion::Hectopascal), + UnitItem(KUnitConversion::Kilopascal), + UnitItem(KUnitConversion::Pascal), + UnitItem(KUnitConversion::Millibar), + UnitItem(KUnitConversion::InchesOfMercury), + UnitItem(KUnitConversion::MillimetersOfMercury), + }; + + return new AbstractUnitListModel(items, engine); +} + +static QObject *windSpeedUnitListModelSingletonTypeProvider(QQmlEngine *engine, QJSEngine * /*scriptEngine*/) +{ + const QList items{ + UnitItem(KUnitConversion::MeterPerSecond), + UnitItem(KUnitConversion::KilometerPerHour), + UnitItem(KUnitConversion::MilePerHour), + UnitItem(KUnitConversion::Knot), + UnitItem(KUnitConversion::Beaufort), + }; + + return new AbstractUnitListModel(items, engine); +} + +static QObject *visibilityUnitListModelSingletonTypeProvider(QQmlEngine *engine, QJSEngine * /*scriptEngine*/) +{ + QList items{UnitItem(KUnitConversion::Kilometer), UnitItem(KUnitConversion::Mile)}; + + return new AbstractUnitListModel(items, engine); +} + +static QObject *utilSingletonTypeProvider(QQmlEngine * /*engine*/, QJSEngine * /*scriptEngine*/) +{ + return new Util(); +} + +class WeatherPlugin : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") + +public: + void registerTypes(const char *uri) override + { + Q_ASSERT(QLatin1String(uri) == QLatin1String("org.kde.plasma.private.weather")); + + qmlRegisterSingletonType(uri, 1, 0, "TemperatureUnitListModel", temperatureUnitListModelSingletonTypeProvider); + qmlRegisterSingletonType(uri, 1, 0, "PressureUnitListModel", pressureUnitListModelSingletonTypeProvider); + qmlRegisterSingletonType(uri, 1, 0, "WindSpeedUnitListModel", windSpeedUnitListModelSingletonTypeProvider); + qmlRegisterSingletonType(uri, 1, 0, "VisibilityUnitListModel", visibilityUnitListModelSingletonTypeProvider); + qmlRegisterSingletonType(uri, 1, 0, "Util", utilSingletonTypeProvider); + qmlRegisterType(uri, 1, 0, "LocationListModel"); + } +}; + +#include "plugin.moc" diff --git a/applets/weather/plugin/util.cpp b/applets/weather/plugin/util.cpp new file mode 100644 index 00000000..29154e48 --- /dev/null +++ b/applets/weather/plugin/util.cpp @@ -0,0 +1,76 @@ +/* + * SPDX-FileCopyrightText: 2018 Friedrich W. H. Kossebau + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "util.h" + +// KF +#include +#include +// Qt +#include +#include +#include +// Std +#include + +template +T clampValue(T value, int decimals) +{ + const T mul = std::pow(static_cast(10), decimals); + return int(value * mul) / mul; +} + +Util::Util(QObject *parent) + : QObject(parent) +{ +} + +KUnitConversion::Converter Util::m_converter; + +QString Util::existingWeatherIconName(const QString &iconName) const +{ + const bool isValid = !iconName.isEmpty() && QIcon::hasThemeIcon(iconName); + return isValid ? iconName : QStringLiteral("weather-not-available"); +} + +QString Util::temperatureToDisplayString(int displayUnitType, double value, int valueUnitType, bool rounded, bool degreesOnly) const +{ + KUnitConversion::Value v(value, static_cast(valueUnitType)); + v = v.convertTo(static_cast(displayUnitType)); + + const QString unit = degreesOnly ? i18nc("Degree, unit symbol", "°") : v.unit().symbol(); + + if (rounded) { + int tempNumber = qRound(v.number()); + return i18nc("temperature unitsymbol", "%1 %2", tempNumber, unit); + } + + const QString formattedTemp = QLocale().toString(clampValue(v.number(), 1), 'f', 1); + return i18nc("temperature unitsymbol", "%1 %2", formattedTemp, unit); +} + +QString Util::valueToDisplayString(int displayUnitType, double value, int valueUnitType, int precision) const +{ + KUnitConversion::Value v(value, static_cast(valueUnitType)); + v = v.convertTo(static_cast(displayUnitType)); + + // TODO: fix KUnitConversion to do locale encoded values and use that + const QString formattedValue = QLocale().toString(clampValue(v.number(), precision), 'f', precision); + return i18nc("value unitsymbol", "%1 %2", formattedValue, v.unit().symbol()); +} + +QString Util::percentToDisplayString(double value) const +{ + const QString formattedPercentValue = QLocale().toString(clampValue(value, 0), 'f', 0); + return i18nc("value percentsymbol", "%1 %", formattedPercentValue); +} + +QString Util::nameFromUnitId(KUnitConversion::UnitId unitId) +{ + const KUnitConversion::Unit unit = m_converter.unit(unitId); + QString unitDescription = i18nc("@item %1 is a unit description and %2 its unit symbol", "%1 (%2)", unit.description(), unit.symbol()); + return unitDescription; +} diff --git a/applets/weather/plugin/util.h b/applets/weather/plugin/util.h new file mode 100644 index 00000000..c5077cb2 --- /dev/null +++ b/applets/weather/plugin/util.h @@ -0,0 +1,48 @@ +/* + * SPDX-FileCopyrightText: 2018 Friedrich W. H. Kossebau + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef UTIL_H +#define UTIL_H + +// KF +#include +// Qt +#include + +class Util : public QObject +{ + Q_OBJECT + +public: + explicit Util(QObject *parent = nullptr); + +public: + enum Status { + Normal = 0, + Connecting, + NeedsConfiguration, + Timeout, + }; + Q_ENUM(Status); + +public: + /** + * Returns the @p iconName if the current icon theme contains an icon with that name, + * otherwise returns "weather-not-available" (expecting the icon theme to have that in any case). + */ + Q_INVOKABLE QString existingWeatherIconName(const QString &iconName) const; + + Q_INVOKABLE QString temperatureToDisplayString(int displayUnitType, double value, int valueUnitType, bool rounded = false, bool degreesOnly = false) const; + Q_INVOKABLE QString valueToDisplayString(int displayUnitType, double value, int valueUnitType, int precision = 0) const; + Q_INVOKABLE QString percentToDisplayString(double value) const; + + static QString nameFromUnitId(KUnitConversion::UnitId unitId); + +private: + static KUnitConversion::Converter m_converter; +}; + +#endif diff --git a/applets/weather/weatherapplet.cpp b/applets/weather/weatherapplet.cpp new file mode 100644 index 00000000..18ae1f68 --- /dev/null +++ b/applets/weather/weatherapplet.cpp @@ -0,0 +1,64 @@ +/* + * SPDX-FileCopyrightText: 2007-2009 Shawn Starr + * SPDX-FileCopyrightText: 2008 Marco Martin + * SPDX-FileCopyrightText: 2012 Luís Gabriel Lima + * SPDX-FileCopyrightText: 2017-2018 Friedrich W. H. Kossebau + * SPDX-FileCopyrightText: 2022 Ismael Asensio + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "weatherapplet.h" + +#include +#include + +// KF +#include +#include +#include + +using namespace KUnitConversion; + +WeatherApplet::WeatherApplet(QObject *parent, const KPluginMetaData &data, const QVariantList &args) + : Plasma::Applet(parent, data, args) +{ + Plasma5Support::DataEngine *dataengine = dataEngine(QStringLiteral("weather")); + const QVariantList plugins = dataengine->containerForSource(QLatin1String("ions"))->data().values(); + for (const QVariant &plugin : plugins) { + const QStringList pluginInfo = plugin.toString().split(QLatin1Char('|')); + if (pluginInfo.count() > 1) { + m_providers[pluginInfo[1]] = pluginInfo[0]; + } + } + Q_EMIT providersChanged(); + + setDefaultUnits(); +} + +WeatherApplet::~WeatherApplet() +{ +} + +// Plasma XML configuration is loaded at runtime, so it's not possible to set locale aware defaults. +// We set them here if the corresponding key is not already in the configuration +void WeatherApplet::setDefaultUnits() +{ + KConfigGroup cfg = config().group(QStringLiteral("Units")); + const bool isMetric = (QLocale().measurementSystem() == QLocale::MetricSystem); + + auto setLocaleAwareDefault = [&cfg, isMetric](const QString &key, KUnitConversion::UnitId metricDefault, KUnitConversion::UnitId imperialDefault) { + if (!cfg.hasKey(key)) { + cfg.writeEntry(key, static_cast(isMetric ? metricDefault : imperialDefault)); + } + }; + + setLocaleAwareDefault(QStringLiteral("temperatureUnit"), KUnitConversion::Celsius, KUnitConversion::Fahrenheit); + setLocaleAwareDefault(QStringLiteral("speedUnit"), KUnitConversion::MeterPerSecond, KUnitConversion::MilePerHour); + setLocaleAwareDefault(QStringLiteral("pressureUnit"), KUnitConversion::Hectopascal, KUnitConversion::InchesOfMercury); + setLocaleAwareDefault(QStringLiteral("visibilityUnit"), KUnitConversion::Kilometer, KUnitConversion::Mile); +} + +K_PLUGIN_CLASS(WeatherApplet) + +#include "weatherapplet.moc" diff --git a/applets/weather/weatherapplet.h b/applets/weather/weatherapplet.h new file mode 100644 index 00000000..902d9fba --- /dev/null +++ b/applets/weather/weatherapplet.h @@ -0,0 +1,40 @@ +/* + * SPDX-FileCopyrightText: 2007-2009 Shawn Starr + * SPDX-FileCopyrightText: 2012 Luís Gabriel Lima + * SPDX-FileCopyrightText: 2017-2018 Friedrich W. H. Kossebau + * SPDX-FileCopyrightText: 2022 Ismael Asensio + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef WEATHERAPPLET_H +#define WEATHERAPPLET_H + +#include +#include + +class WeatherApplet : public Plasma::Applet, public Plasma5Support::DataEngineConsumer +{ + Q_OBJECT + // used for making this information available to the config pages + Q_PROPERTY(bool needsToBeSquare MEMBER m_needsToBeSquare NOTIFY needsToBeSquareChanged FINAL) + + Q_PROPERTY(QVariantMap providers MEMBER m_providers NOTIFY providersChanged FINAL) + +public: + WeatherApplet(QObject *parent, const KPluginMetaData &data, const QVariantList &args); + ~WeatherApplet() override; + +Q_SIGNALS: + void needsToBeSquareChanged(); + void providersChanged(); + +private: + void setDefaultUnits(); + +private: + QVariantMap m_providers; + bool m_needsToBeSquare = false; +}; + +#endif diff --git a/applets/weather/wind-arrows.svgz b/applets/weather/wind-arrows.svgz new file mode 100644 index 0000000000000000000000000000000000000000..666ec85dc338c09d4a1d707a6f53f03feb991e27 GIT binary patch literal 2164 zcmV-)2#fb0iwFqpsvcVa0|4ckOK;mq636fP6bV(TCwF{G+~_m9PY7kTmX<9vp`7Ijlri|L3ZbcBjU zR^{d5b~^gw)33yiP}8Q1Je^gGVmew>qn|I|T{K^B-ywvc@}imK*>rT*w)d0q`2L}u zB~^Vp&a-hbE9S+bZN@BN<56Ec>x*Y~k+$X6BCF=}YSDBx7tQ;P!gYQVOD~tpWGT8r z%sDrve9Sq45~O)rwCN++t_ov3RFzXo$KdNr4l6ZjU{&tne=HshNmD)4S#bmPieyo= z;}4%cL?@w1-sT(4l#4G-mfjazy$0iI1=4xZ+^1R5jDx+aU*^*h3`sggyWe+3d3)Ew zuCdkcWtq2k(~-5^udPk>hA^T@US+1bIla+20{wQQ0w*U;l6)~AW%i9mnjk}4%N zMw~KBs345}eRK(RE@I2R+5Br+EPYD|-KV#(wX>?8j^5vNpV7*3UDbI}2bbp3HITf?+B?eGb%hcYh?)rphzBaD3=^fq^CUM;61 zzU%ahS1rkU(V2)d*ewyZ$?z~4zmajN7%Zz>JDR&{>E|;Ywe>@>Y4kH`w#>1c&PT4V ztH;CsZ&fv)j&vd!XGZLLGpMCaqJ)(DK3EjV6T>-m>Y3R+9$2@v%Wt23#y3wN%X#^& z$onnsyM3r@2pnXVJ{5J3{AE|Hhpn6Usw$vXHD%l3$~lgM1#apRno3tLhG}&8MebT) zQd8NR!ysl=_Qj9xCY?3K2#wdh_p1)u6e_h({&IeN^0w^`z~I~0eFNVczLeOw7Y^SJ z9{%@w*BTr8vfdf}VZB?6jeTM7{D<}K7-sC5$y3Y@cR!q*0o~hYCjV^j`o}H3!dkzu z_aBFy;aJL-_0E4Fb_{d=!rljBcQM|-H+J@U(WZIYru_{eSdtmuswziYS zn`rFDF&(W{&4m4Q1A8=CfO_w~k>1g?b@ioKxmMp-YGy){;3TthQ2^bO=HCx#U2Iza zs>;O#I4J61?S9PQw%$%;u;nQTsJc#{wz~1w>gJ{?+6fJ|IIjM}y-(Y_z00fxG1qvE zNd`M#)Lwyr$fi^{s=3;~F83-tbrlq4)JWamYx^s;0@VoYfZoddXTQm;TEMcjfE)n$ zHEkd2VsrESzWA|AVn{zAe~lx?us}p$rjWpd!yk^m#Uj}(ZJ^g(KzH}H+tD@pUD^M$ zvoR3e?h$$Qc?gpmyuBDo!p|B@IQTj$l{s*uJOm}ok8x#)5wVG4%sR~A1i&bhTq6PJ1VgFDaQ$fK40VXZm8ZFZTWI1Ov~V(-2f@6dGF}d| zJ8PI}VyIA7nt>tKmv;>Q=~t)OY}2d(PNk*6DiL0)af675m>UO&Lz_sexMiZFS;4^< z`=J8j28Ay;xK2g$=V3T+NcNZGEY2KfU{g6W6lWjHjW$ZWI?Z~UW-Ak69h|nXNu1=C zN(VTrF6>I_!>+cP35M1I7>9_AVvxomZl4V2XJFny^EmZO*jd9I<*XbWoL72TyuQ@n z+c+}|m>Q-n76Jw#9Y_YkBpCe%l!ZXtOJx7&M6`Z|+14FmS5yn~bEyqP5-nJ_T-5K9vK z=b+v`-W?}D#JR`npeGKzAMuQ4Udfd=(9CQ%!^RyzO*Pe6^F%nNfzI&og9IhwpW20| zACciGJbKVih4nD1k5eGx%yY&hNQ$9pwkTQN1naF#+wYwO5y~)wy9Xl5ag6|Qs)ZH~ zE1obzwNyv}>!3;?HY=7$EtIfGnZ$DHkBrhJj$2A4`twBR#erxar$QiLpScVPy21@* zJLnek259fdkWPGXWiYTyl@n5;C_cJS+zAr24$A(_7!(Ow$8H;sk?Ld*MLrIDEOL|* zVH=0-bHqBnQ}Gh2y>NMbs`+jnMAAf9t+~bB`xshjj(Yup!wVT`|FIs~d-iuK88Qs% zqhts>^VD9@6oZits^twt2tW8P26a+lVH2&HgaadqiID!a3`F+dy&$_UXq@c%^~Zr+ zAEiOqnI}Smo*0gGCs-_d3#`@FnelgIDF_b + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + */ + +import QtQuick 2.0 +import org.kde.plasma.configuration 2.0 + +ConfigModel { + ConfigCategory { + name: i18nc("@title", "General") + icon: "plasma" + source: "ConfigGeneral.qml" + } + + ConfigCategory { + name: i18nc("@title", "Appearance") + icon: "preferences-desktop-color" + source: "ConfigAppearance.qml" + } +} diff --git a/applets/webbrowser/package/contents/config/main.xml b/applets/webbrowser/package/contents/config/main.xml new file mode 100644 index 00000000..2605aff7 --- /dev/null +++ b/applets/webbrowser/package/contents/config/main.xml @@ -0,0 +1,43 @@ + + + + + + + false + + + https://www.kde.org/ + + + https://www.kde.org/ + + + true + + + 600 + + + 100 + + + + + + + + + + + + true + + + true + + + diff --git a/applets/webbrowser/package/contents/ui/ConfigAppearance.qml b/applets/webbrowser/package/contents/ui/ConfigAppearance.qml new file mode 100644 index 00000000..a872d5f0 --- /dev/null +++ b/applets/webbrowser/package/contents/ui/ConfigAppearance.qml @@ -0,0 +1,126 @@ +/* + * SPDX-FileCopyrightText: 2023 Fushan Wen + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls as QQC2 + +import org.kde.iconthemes as KIconThemes +import org.kde.kirigami 2.20 as Kirigami +import org.kde.ksvg 1.0 as KSvg +import org.kde.kcmutils as KCM + +import org.kde.plasma.core as PlasmaCore + +KCM.SimpleKCM { + property string cfg_icon: plasmoid.configuration.icon + property alias cfg_useFavIcon: useFavIcon.checked + property alias cfg_enableNavigationBar: enableNavigationBar.checked + + + Kirigami.FormLayout { + + QQC2.ButtonGroup { + id: iconGroup + } + + QQC2.RadioButton { + id: useFavIcon + + Kirigami.FormData.label: i18nc("@title:group", "Icon:") + text: i18nc("@option:radio", "Website's favicon") + + QQC2.ButtonGroup.group: iconGroup + } + + RowLayout { + spacing: Kirigami.Units.smallSpacing + + QQC2.RadioButton { + id: useSystemIcon + checked: !cfg_useFavIcon + + QQC2.ButtonGroup.group: iconGroup + } + + QQC2.Button { + id: iconButton + + implicitWidth: previewFrame.width + Kirigami.Units.smallSpacing * 2 + implicitHeight: previewFrame.height + Kirigami.Units.smallSpacing * 2 + enabled: useSystemIcon.checked + hoverEnabled: true + + Accessible.name: i18nc("@action:button", "Change Web Browser's icon") + Accessible.description: i18nc("@info:whatsthis", "Current icon is %1. Click to open menu to change the current icon or reset to the default icon.", cfg_icon) + Accessible.role: Accessible.ButtonMenu + + QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay + QQC2.ToolTip.text: i18nc("@info:tooltip", "Icon name is \"%1\"", cfg_icon) + QQC2.ToolTip.visible: iconButton.hovered && cfg_icon.length > 0 + + onPressed: iconMenu.opened ? iconMenu.close() : iconMenu.open() + + KIconThemes.IconDialog { + id: iconDialog + onIconNameChanged: { + cfg_icon = iconName || ""; + } + } + + KSvg.FrameSvgItem { + id: previewFrame + anchors.centerIn: parent + imagePath: plasmoid.formFactor === PlasmaCore.Types.Vertical || plasmoid.formFactor === PlasmaCore.Types.Horizontal + ? "widgets/panel-background" : "widgets/background" + width: Kirigami.Units.iconSizes.large + fixedMargins.left + fixedMargins.right + height: Kirigami.Units.iconSizes.large + fixedMargins.top + fixedMargins.bottom + + Kirigami.Icon { + anchors.centerIn: parent + width: Kirigami.Units.iconSizes.large + height: width + source: cfg_icon || "applications-internet" + } + } + + QQC2.Menu { + id: iconMenu + + // Appear below the button + y: +parent.height + + QQC2.MenuItem { + text: i18nc("@item:inmenu Open icon chooser dialog", "Choose…") + icon.name: "document-open-folder" + Accessible.description: i18nc("@info:whatsthis", "Choose an icon for Web Browser") + onClicked: iconDialog.open() + } + QQC2.MenuItem { + text: i18nc("@item:inmenu Reset icon to default", "Reset to default icon") + icon.name: "edit-clear" + enabled: cfg_icon !== "" + onClicked: cfg_icon = "" + } + } + } + } + + Item { + Kirigami.FormData.isSection: true + } + + QQC2.CheckBox { + id: enableNavigationBar + text: i18nc("@option:check", "Display Navigation Bar") + checked: enableNavigationBar.checked + + onCheckedChanged: { + cfg_enableNavigationBar = enableNavigationBar.checked + } + } + } +} diff --git a/applets/webbrowser/package/contents/ui/ConfigGeneral.qml b/applets/webbrowser/package/contents/ui/ConfigGeneral.qml new file mode 100644 index 00000000..f114bf6f --- /dev/null +++ b/applets/webbrowser/package/contents/ui/ConfigGeneral.qml @@ -0,0 +1,145 @@ +import QtQuick +import QtQuick.Layouts 1.3 +import QtQuick.Controls 2.12 as QQC2 +import org.kde.plasma.components 3.0 as PlasmaComponents3 + +import org.kde.kirigami 2.20 as Kirigami +import org.kde.kcmutils as KCM + +KCM.SimpleKCM { + property alias cfg_useMinViewWidth: useMinViewWidth.checked + property alias cfg_minViewWidth: minViewWidth.value + property alias cfg_constantZoomFactor: constantZoomFactor.value + property alias cfg_useDefaultUrl: useDefaultUrlRadio.checked + property alias cfg_defaultUrl: defaultUrl.text + + Kirigami.FormLayout { + QQC2.ButtonGroup { id: defaultUrlGroup } + + QQC2.RadioButton { + id: useUrlRadio + text: i18nc("@option:radio", "Load last-visited page") + checked: !cfg_useDefaultUrl + QQC2.ButtonGroup.group: defaultUrlGroup + + Kirigami.FormData.label: i18nc("@title:group", "On startup:") + + onToggled: cfg_useDefaultUrl = false; + } + + QQC2.RadioButton { + id: useDefaultUrlRadio + text: i18nc("@option:radio", "Always load this page:") + checked: cfg_useDefaultUrl + QQC2.ButtonGroup.group: defaultUrlGroup + + onToggled: { + cfg_useDefaultUrl = true; + defaultUrl.forceActiveFocus(); + defaultUrl.selectAll(); + } + } + + RowLayout { + spacing: 0 + Layout.fillWidth: true + + // HACK: Workaround for Kirigami bug 434625 + // due to which a simple Layout.leftMargin doesn't work + Item { implicitWidth: Kirigami.Units.gridUnit } + + PlasmaComponents3.TextField { + id: defaultUrl + onAccepted: { + let url = text; + if (url.indexOf(":/") < 0) { + url = "http://" + url; + } + } + + Layout.fillWidth: true + + text: cfg_defaultUrl + enabled: useDefaultUrlRadio.checked + Accessible.description: text.length > 0 ? text : i18nc("@info", "Type a URL") + } + } + + Item { + Kirigami.FormData.isSection: true + } + + QQC2.ButtonGroup { id: zoomGroup } + + RowLayout { + Kirigami.FormData.label: i18nc("@title:group", "Content scaling:") + + QQC2.RadioButton { + id: useConstantZoom + text: i18nc("@option:radio", "Fixed scale:") + checked: !cfg_useMinViewWidth + + QQC2.ButtonGroup.group: zoomGroup + + onClicked: { + constantZoomFactor.forceActiveFocus(); + } + } + + QQC2.SpinBox { + id: constantZoomFactor + editable: true + enabled: useConstantZoom.checked + + validator: RegularExpressionValidator { + regularExpression: /[0-9]?[0-9]{2}[ ]?%/ + } + + textFromValue: function(value) { + return value+"%"; + } + + valueFromText: function(text) { + return text.split(" ")[0].split("%")[0]; + } + + from: 25 + to: 500 + } + } + + RowLayout { + QQC2.RadioButton { + id: useMinViewWidth + text: i18nc("@option:radio", "Automatic scaling if width is below") + + QQC2.ButtonGroup.group: zoomGroup + + onClicked: { + minViewWidth.forceActiveFocus(); + } + } + + QQC2.SpinBox { + id: minViewWidth + editable: true + enabled: useMinViewWidth.checked + + validator: RegularExpressionValidator { + regularExpression: /[0-9]?[0-9]{3}[ ]?px/ + } + + textFromValue: function(value) { + return value+"px"; + } + + valueFromText: function(text) { + return text.split(" ")[0].split("px")[0]; + } + + from: 320 + to: 3840 + } + } + } +} diff --git a/applets/webbrowser/package/contents/ui/main.qml b/applets/webbrowser/package/contents/ui/main.qml new file mode 100644 index 00000000..379e586c --- /dev/null +++ b/applets/webbrowser/package/contents/ui/main.qml @@ -0,0 +1,290 @@ +/* + * SPDX-FileCopyrightText: 2014, 2016 Mikhail Ivchenko + * SPDX-FileCopyrightText: 2018 Kai Uwe Broulik + * SPDX-FileCopyrightText: 2020 Sora Steenvoort + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick +import QtWebEngine +import QtQuick.Layouts 1.1 +import org.kde.plasma.components 3.0 as PlasmaComponents3 +import org.kde.plasma.extras 2.0 as PlasmaExtras +import org.kde.kirigami 2.20 as Kirigami +import org.kde.plasma.plasmoid 2.0 + +PlasmoidItem { + id: root + + switchWidth: Kirigami.Units.gridUnit * 16 + switchHeight: Kirigami.Units.gridUnit * 23 + + // Only exists because the default CompactRepresentation doesn't expose + // a way to display arbitrary images; it can only show icons. + // TODO remove once it gains that feature. + compactRepresentation: Loader { + id: favIconLoader + active: Plasmoid.configuration.useFavIcon + asynchronous: true + sourceComponent: Image { + asynchronous: true + cache: false + fillMode: Image.PreserveAspectFit + source: Plasmoid.configuration.favIcon + } + + TapHandler { + property bool wasExpanded: false + + acceptedButtons: Qt.LeftButton + + onPressedChanged: if (pressed) { + wasExpanded = root.expanded; + } + onTapped: root.expanded = !wasExpanded + } + + Kirigami.Icon { + anchors.fill: parent + visible: favIconLoader.item?.status !== Image.Ready + source: Plasmoid.configuration.icon || Plasmoid.icon + } + } + + fullRepresentation: ColumnLayout { + Layout.minimumWidth: root.switchWidth + Layout.minimumHeight: root.switchHeight + + RowLayout{ + visible: plasmoid.configuration.enableNavigationBar + Layout.fillWidth: true + PlasmaComponents3.Button { + icon.name: "go-previous-symbolic" + onClicked: webview.goBack() + enabled: webview.canGoBack + display: PlasmaComponents3.AbstractButton.IconOnly + text: i18nc("@action:button", "Go Back") + } + PlasmaComponents3.Button { + icon.name: "go-next-symbolic" + onClicked: webview.goForward() + enabled: webview.canGoForward + display: PlasmaComponents3.AbstractButton.IconOnly + text: i18nc("@action:button", "Go Forward") + } + PlasmaComponents3.Button { + id: goHomeButton + icon.name: "go-home-symbolic" + onClicked: webview.url = plasmoid.configuration.defaultUrl + display: PlasmaComponents3.AbstractButton.IconOnly + visible: plasmoid.configuration.useDefaultUrl + text: i18nc("@action:button", "Go Home") + PlasmaComponents3.ToolTip { + text: goHomeButton.text + } + Accessible.name: goHomeButton.text + Accessible.description: i18n("Open Default URL") + } + PlasmaComponents3.TextField { + Layout.fillWidth: true + onAccepted: { + var url = text; + if (url.indexOf(":/") < 0) { + url = "http://" + url; + } + webview.url = url; + } + onActiveFocusChanged: { + if (activeFocus) { + selectAll(); + } + } + + text: webview.url + + Accessible.description: text.length > 0 ? text : i18nc("@info", "Type a URL") + } + + // this shows page-related information such as blocked popups + PlasmaComponents3.ToolButton { + id: infoButton + + // callback invoked when button is clicked + property var cb + + // button itself adds sufficient visual padding + Layout.leftMargin: -parent.spacing + Layout.rightMargin: -parent.spacing + + onClicked: cb(); + + PlasmaComponents3.ToolTip { + id: tooltip + } + + function show(text, icon, tooltipText, cb) { + infoButton.text = text; + infoButton.icon.name = icon; + tooltip.text = tooltipText; + infoButton.cb = cb; + infoButton.visible = true; + } + + function dismiss() { + infoButton.visible = false; + } + } + + PlasmaComponents3.Button { + display: PlasmaComponents3.AbstractButton.IconOnly + icon.name: webview.loading ? "process-stop-symbolic" : "view-refresh-symbolic" + text: webview.loading ? i18nc("@action:button", "Stop Loading This Page") : i18nc("@action:button", "Reload This Page") + onClicked: webview.loading ? webview.stop() : webview.reload() + } + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + + // TODO use contentsSize but that crashes, now mostly for some sane initial size + Layout.preferredWidth: Kirigami.Units.gridUnit * 36 + Layout.preferredHeight: Kirigami.Units.gridUnit * 18 + + // Binding it to e.g. width will be super slow on resizing + Timer { + id: updateZoomTimer + interval: 100 + + readonly property int minViewWidth: plasmoid.configuration.minViewWidth + readonly property bool useMinViewWidth: plasmoid.configuration.useMinViewWidth + readonly property int constantZoomFactor: plasmoid.configuration.constantZoomFactor + + onTriggered: { + var newZoom = 1; + if (useMinViewWidth) { + // Try to fit contents for a smaller screen + newZoom = Math.min(1, webview.width / minViewWidth); + // make sure value is valid + newZoom = Math.max(0.25, newZoom); + } else { + newZoom = constantZoomFactor / 100.0; + } + webview.zoomFactor = newZoom; + // setting the zoom factor does not always work on the first try; also, numbers get rounded + if (Math.round(1000 * webview.zoomFactor) != Math.round(1000 * newZoom)) { + updateZoomTimer.restart(); + } + } + } + + // This reimplements WebEngineView context menu for links to add a "open externally" entry + // since you cannot add custom items there yet + // there's a FIXME comment about that in QQuickWebEngineViewPrivate::contextMenuRequested + PlasmaExtras.Menu { + id: linkContextMenu + visualParent: webview + + property string link + + PlasmaExtras.MenuItem { + text: i18nc("@action:inmenu", "Open Link in Browser") + icon: "internet-web-browser-symbolic" + onClicked: Qt.openUrlExternally(linkContextMenu.link) + } + + PlasmaExtras.MenuItem { + text: i18nc("@action:inmenu", "Copy Link Address") + icon: "edit-copy-symbolic" + onClicked: webview.triggerWebAction(WebEngineView.CopyLinkToClipboard) + } + } + + WebEngineView { + id: webview + anchors.fill: parent + onUrlChanged: plasmoid.configuration.url = url; + Component.onCompleted: url = plasmoid.configuration.useDefaultUrl ? plasmoid.configuration.defaultUrl : plasmoid.configuration.url; + + readonly property bool useMinViewWidth : plasmoid.configuration.useMinViewWidth + + Connections { + target: plasmoid.configuration + + function onMinViewWidthChanged() {updateZoomTimer.start()} + + function onUseMinViewWidthChanged() {updateZoomTimer.start()} + + function onConstantZoomFactorChanged() {updateZoomTimer.start()} + + function onUseConstantZoomChanged() {updateZoomTimer.start()} + } + + onLinkHovered: hoveredUrl => { + if (hoveredUrl.toString() !== "") { + mouseArea.cursorShape = Qt.PointingHandCursor; + } else { + mouseArea.cursorShape = Qt.ArrowCursor; + } + } + + onWidthChanged: { + if (useMinViewWidth) { + updateZoomTimer.start() + } + } + + onLoadingChanged: loadingInfo => { + if (loadingInfo.status === WebEngineLoadingInfo.LoadStartedStatus) { + infoButton.dismiss(); + } else if (loadingInfo.status === WebEngineLoadingInfo.LoadSucceededStatus && useMinViewWidth) { + updateZoomTimer.start(); + } + } + + onContextMenuRequested: request => { + if (request.mediaType === ContextMenuRequest.MediaTypeNone && request.linkUrl.toString() !== "") { + linkContextMenu.link = request.linkUrl; + linkContextMenu.open(request.position.x, request.position.y); + request.accepted = true; + } + } + + onNavigationRequested: request => { + var url = request.url; + + if (request.userInitiated) { + Qt.openUrlExternally(url); + } else { + infoButton.show(i18nc("An unwanted popup was blocked", "Popup blocked"), "document-close-symbolic", + i18n("Click here to open the following blocked popup:\n%1", url), function () { + Qt.openUrlExternally(url); + infoButton.dismiss(); + }); + } + } + + onIconChanged: { + if (loading && icon == "") { + return; + } + Plasmoid.configuration.favIcon = icon.toString().slice(16 /* image://favicon/ */); + } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + acceptedButtons: Qt.BackButton | Qt.ForwardButton + onPressed: mouse => { + if (mouse.button === Qt.BackButton) { + webview.goBack(); + } else if (mouse.button === Qt.ForwardButton) { + webview.goForward(); + } + } + } + } + } +} diff --git a/applets/webbrowser/package/metadata.json b/applets/webbrowser/package/metadata.json new file mode 100644 index 00000000..3e5ecbac --- /dev/null +++ b/applets/webbrowser/package/metadata.json @@ -0,0 +1,129 @@ +{ + "KPackageStructure": "Plasma/Applet", + "KPlugin": { + "Authors": [ + { + "Email": "ematirov@gmail.com", + "Name": "Mikhail Ivchenko", + "Name[ar]": "ميخائيل ايفتشينكو", + "Name[az]": "Mikhail Ivchenko", + "Name[bg]": "Mikhail Ivchenko", + "Name[ca@valencia]": "Mikhail Ivchenko", + "Name[ca]": "Mikhail Ivchenko", + "Name[cs]": "Mikhail Ivchenko", + "Name[da]": "Mikhail Ivchenko", + "Name[de]": "Mikhail Ivchenko", + "Name[en_GB]": "Mikhail Ivchenko", + "Name[eo]": "Miĥail Ivĉenko", + "Name[es]": "Mikhail Ivchenko", + "Name[eu]": "Mikhail Ivchenko", + "Name[fi]": "Mikhail Ivchenko", + "Name[fr]": "Mikhail Ivchenko", + "Name[gl]": "Mikhail Ivchenko", + "Name[he]": "מיכאיל איבצ׳נקו", + "Name[hu]": "Mikhail Ivchenko", + "Name[ia]": "Mikhail Ivchenko", + "Name[id]": "Mikhail Ivchenko", + "Name[is]": "Mikhail Ivchenko", + "Name[it]": "Mikhail Ivchenko", + "Name[ja]": "Mikhail Ivchenko", + "Name[ka]": "Mikhail Ivchenko", + "Name[ko]": "Mikhail Ivchenko", + "Name[lt]": "Mikhail Ivchenko", + "Name[lv]": "Mikhail Ivchenko", + "Name[nl]": "Mikhail Ivchenko", + "Name[nn]": "Mikhail Ivchenko", + "Name[pl]": "Mikhail Ivchenko", + "Name[pt]": "Mikhail Ivchenko", + "Name[pt_BR]": "Mikhail Ivchenko", + "Name[ro]": "Mikhail Ivchenko", + "Name[ru]": "Mikhail Ivchenko", + "Name[sk]": "Mikhail Ivchenko", + "Name[sl]": "Mikhail Ivchenko", + "Name[sv]": "Mikhail Ivchenko", + "Name[tr]": "Mihail İvçenko", + "Name[uk]": "Михайло Івченко", + "Name[vi]": "Mikhail Ivchenko", + "Name[x-test]": "xxMikhail Ivchenkoxx", + "Name[zh_CN]": "Mikhail Ivchenko", + "Name[zh_TW]": "Mikhail Ivchenko" + } + ], + "BugReportUrl": "https://bugs.kde.org/enter_bug.cgi?product=kdeplasma-addons&component=Web Browser", + "Category": "Online Services", + "Description": "Add a webpage on your desktop.", + "Description[ar]": "أضف صفحة ويب لسطح مكتبك", + "Description[az]": "Veb səhifəni İş Masanızda göstərir", + "Description[bg]": "Добавяне на уеб страница на работния плот.", + "Description[ca@valencia]": "Afig una pàgina web a l'escriptori.", + "Description[ca]": "Afegeix una pàgina web a l'escriptori.", + "Description[da]": "Tilføj en hjemmeside på dit skrivebord.", + "Description[de]": "Eine Webseite zu Ihrer Arbeitsfläche hinzufügen.", + "Description[en_GB]": "Add a webpage on your desktop.", + "Description[eo]": "Aldonu retpaĝon sur via labortablo.", + "Description[es]": "Añadir una página web al escritorio.", + "Description[eu]": "Gehitu web-orri bat zure mahaigainean.", + "Description[fi]": "Lisää työpöydälle verkkosivun.", + "Description[fr]": "Ajouter une page Internet à votre bureau.", + "Description[gl]": "Engadir unha páxina web ao escritorio.", + "Description[he]": "הוספת עמוד אינטרנט לשולחן העבודה שלך.", + "Description[hu]": "Weboldal hozzáadása az asztalhoz.", + "Description[ia]": "Adde un pagina web sur tu scriptorio.", + "Description[id]": "Tambahkan halaman web ke desktopmu.", + "Description[is]": "Bættu vefsíðu á skjáborðið þitt.", + "Description[it]": "Aggiungi una pagina web al tuo desktop.", + "Description[ja]": "ウェブページをデスクトップに追加します。", + "Description[ka]": "დაამატეთ ვებგვერდი თქვენს სამშაო მაგიდას.", + "Description[ko]": "바탕 화면에 웹 페이지를 추가합니다.", + "Description[lt]": "Internetinis puslapis darbalaukyje.", + "Description[lv]": "Pievienojiet darbvirsmai tīmekļa vietni.", + "Description[nl]": "Een webpagina toevoegen aan uw bureaublad.", + "Description[nn]": "Legg ei nettside på skrivebordet", + "Description[pl]": "Dodaje stronę internetową na pulpit.", + "Description[pt]": "Adicionar uma página Web ao seu ecrã.", + "Description[pt_BR]": "Adiciona um site na sua área de trabalho.", + "Description[ro]": "Adaugă o pagină web pe birou.", + "Description[ru]": "Просмотр веб-страницы на рабочем столе", + "Description[sk]": "Pridajte webstránku na vašu pracovnú plochu", + "Description[sl]": "Dodaj spletno stran na vaše namizje.", + "Description[sv]": "Lägg till en webbsida på skrivbordet.", + "Description[tr]": "Masaüstüne bir web sayfası ekle", + "Description[uk]": "Додати сторінку інтернету на вашу стільницю.", + "Description[vi]": "Thêm một trang web vào bàn làm việc", + "Description[x-test]": "xxAdd a webpage on your desktop.xx", + "Description[zh_CN]": "添加网页到您的桌面", + "Description[zh_TW]": "加一個網站在您的桌面上。", + "Icon": "applications-internet", + "Id": "org.kde.plasma.webbrowser", + "License": "GPL-2.0+", + "Name": "Web Browser", + "Name[ar]": "متصفّح الوبّ", + "Name[bg]": "Уеб браузър", + "Name[ca@valencia]": "Navegador web", + "Name[ca]": "Navegador web", + "Name[cs]": "Internetový prohlížeč", + "Name[de]": "Webbrowser", + "Name[en_GB]": "Web Browser", + "Name[es]": "Navegador web", + "Name[eu]": "Web-arakatzailea", + "Name[fr]": "Navigateur Internet", + "Name[gl]": "Navegador web", + "Name[he]": "דפדפן", + "Name[hu]": "Webböngésző", + "Name[ia]": "Navigator Web", + "Name[it]": "Browser web", + "Name[ka]": "ვებ-ბრაუზერი", + "Name[nn]": "Nettlesar", + "Name[pl]": "Przeglądarka sieciowa", + "Name[ru]": "Веб-браузер", + "Name[sl]": "Spletni brskalnik", + "Name[sv]": "Webbläsare", + "Name[tr]": "Web Tarayıcısı", + "Name[uk]": "Браузер", + "Name[x-test]": "xxWeb Browserxx", + "Name[zh_CN]": "网页浏览器", + "Name[zh_TW]": "網頁瀏覽器", + "Website": "https://kde.org/plasma-desktop" + }, + "X-Plasma-API-Minimum-Version": "6.0" +} diff --git a/dict/CMakeLists.txt b/dict/CMakeLists.txt new file mode 100644 index 00000000..84d44bd2 --- /dev/null +++ b/dict/CMakeLists.txt @@ -0,0 +1,9 @@ +add_library(engine_dict_static STATIC dictengine.cpp) +set_property(TARGET engine_dict_static PROPERTY POSITION_INDEPENDENT_CODE ON) +target_compile_definitions(engine_dict_static PRIVATE -DTRANSLATION_DOMAIN="plasma_addons_engine_dict") + +target_link_libraries (engine_dict_static + KF6::I18n + Qt::Network +) + diff --git a/dict/Messages.sh b/dict/Messages.sh new file mode 100644 index 00000000..1bc6baf1 --- /dev/null +++ b/dict/Messages.sh @@ -0,0 +1,2 @@ +#! /usr/bin/env bash +$XGETTEXT *.cpp -o $podir/plasma_addons_engine_dict.pot diff --git a/dict/dictengine.cpp b/dict/dictengine.cpp new file mode 100644 index 00000000..118a05c4 --- /dev/null +++ b/dict/dictengine.cpp @@ -0,0 +1,289 @@ +/* + SPDX-FileCopyrightText: 2007 Jeff Cooper + SPDX-FileCopyrightText: 2007 Thomas Georgiou + SPDX-FileCopyrightText: 2022 Alexander Lohnau + + SPDX-License-Identifier: LGPL-2.0-only +*/ + +#include "dictengine.h" + +#include +#include + +#include +#include +#include +#include + +using namespace std::chrono_literals; + +DictEngine::DictEngine(QObject *parent) + : QObject(parent) + , m_dictNames{QByteArrayLiteral("wn")} // In case we need to switch it later + , m_serverName(QStringLiteral("dict.org")) // Default, good dictionary + , m_definitionResponses{ + QByteArrayLiteral("250"), /**< ok (optional timing information here) */ + QByteArrayLiteral("550"), /**< Invalid database */ + QByteArrayLiteral("501"), /**< Syntax error, illegal parameters */ + QByteArrayLiteral("503"), /**< Command parameter not implemented */ + } +{ + m_definitionTimer.setInterval(30s); + m_definitionTimer.setSingleShot(true); + connect(&m_definitionTimer, &QTimer::timeout, this, &DictEngine::slotDefinitionReadFinished); +} + +DictEngine::~DictEngine() +{ +} + +void DictEngine::setDict(const QByteArray &dict) +{ + m_dictNames = dict.split(','); +} + +void DictEngine::setServer(const QString &server) +{ + m_serverName = server; +} + +static QString wnToHtml(const QString &word, QByteArray &text) +{ + QList splitText = text.split('\n'); + QString def; + def += QLatin1String("

\n"); + static QRegularExpression linkRx(QStringLiteral("{(.*?)}")); + + bool isFirst = true; + while (!splitText.empty()) { + // 150 n definitions retrieved - definitions follow + // 151 word database name - text follows + // 250 ok (optional timing information here) + // 552 No match + QString currentLine = splitText.takeFirst(); + if (currentLine.startsWith(QLatin1String("151"))) { + isFirst = true; + continue; + } + + if (currentLine.startsWith('.')) { + def += QLatin1String(""); + continue; + } + + // Don't early return if there are multiple dicts + if (currentLine.startsWith("552") || currentLine.startsWith("501")) { + def += QStringLiteral("
%1
\n
%2
").arg(word, i18n("No match found for %1", word)); + continue; + } + + if (!(currentLine.startsWith(QLatin1String("150")) || currentLine.startsWith(QLatin1String("151")) || currentLine.startsWith(QLatin1String("250")))) { + // Handle links + int offset = 0; + QRegularExpressionMatchIterator it = linkRx.globalMatch(currentLine); + while (it.hasNext()) { + QRegularExpressionMatch match = it.next(); + QUrl url; + url.setScheme("dict"); + url.setPath(match.captured(1)); + const QString linkText = QStringLiteral("
%2").arg(url.toString(), match.captured(1)); + currentLine.replace(match.capturedStart(0) + offset, match.capturedLength(0), linkText); + offset += linkText.length() - match.capturedLength(0); + } + + if (isFirst) { + def += "
" + currentLine + "
\n
"; + isFirst = false; + continue; + } else { + static QRegularExpression newLineRx(QStringLiteral("([1-9]{1,2}:)")); + if (currentLine.contains(newLineRx)) { + def += QLatin1String("\n
\n"); + } + static QRegularExpression makeMeBoldRx(QStringLiteral("^([\\s\\S]*[1-9]{1,2}:)")); + currentLine.replace(makeMeBoldRx, QLatin1String("\\1")); + def += currentLine; + continue; + } + } + } + + def += QLatin1String("
"); + return def; +} + +void DictEngine::getDefinition() +{ + // Clear the old data to prepare for a new lookup + m_definitionData.clear(); + + connect(m_tcpSocket, &QTcpSocket::readyRead, this, &DictEngine::slotDefinitionReadyRead); + + m_tcpSocket->readAll(); + + // Command Pipelining: https://datatracker.ietf.org/doc/html/rfc2229#section-4 + QByteArray command; + for (const QByteArray &dictName : std::as_const(m_dictNames)) { + command += QByteArrayLiteral("DEFINE ") + dictName + QByteArrayLiteral(" \"") + m_currentWord.toUtf8() + QByteArrayLiteral("\"\n"); + } + + m_tcpSocket->write(command); + m_tcpSocket->flush(); + + m_definitionTimer.start(); +} + +void DictEngine::getDicts() +{ + m_tcpSocket->readAll(); + QByteArray ret; + + m_tcpSocket->write(QByteArray("SHOW DB\n")); + m_tcpSocket->flush(); + + if (m_tcpSocket->waitForReadyRead()) { + while (!ret.contains("250") && !ret.contains("420") && !ret.contains("421") && m_tcpSocket->waitForReadyRead()) { + ret += m_tcpSocket->readAll(); + } + } + + QMap availableDicts; +#if (defined(__GNUC__) && __GNUC__ >= 12) || !defined(__GNUC__) + for (const QByteArrayView curr : QByteArrayView(ret) | std::views::split('\n')) { +#else + for (const QByteArrayView curr : ret.split('\n')) { +#endif + if (curr.endsWith("420") || curr.startsWith("421")) { + // TODO: what happens if the server is down + } + if (curr.startsWith("554")) { + // TODO: What happens if no DB available? + // TODO: Eventually there will be functionality to change the server... + break; + } + + // ignore status code and empty lines + if (curr.startsWith("250") || curr.startsWith("110") || curr.empty()) { + continue; + } + + if (!curr.startsWith('-') && !curr.startsWith('.')) { + const QString line = QString::fromUtf8(curr).trimmed(); + const QString id = line.section(' ', 0, 0); + QString description = line.section(' ', 1); + if (description.startsWith('"') && description.endsWith('"')) { + description.remove(0, 1); + description.chop(1); + } + availableDicts.insert(id, description); + } + } + + m_tcpSocket->disconnectFromHost(); + m_availableDictsCache.insert(m_serverName, availableDicts); + Q_EMIT dictsRecieved(availableDicts); + Q_EMIT dictLoadingChanged(false); +} + +void DictEngine::requestDicts() +{ + if (m_availableDictsCache.contains(m_serverName)) { + Q_EMIT dictsRecieved(m_availableDictsCache.value(m_serverName)); + return; + } + if (m_tcpSocket) { + m_tcpSocket->abort(); // stop if lookup is in progress and new query is requested + m_tcpSocket->deleteLater(); + m_tcpSocket = nullptr; + } + + Q_EMIT dictLoadingChanged(true); + m_tcpSocket = new QTcpSocket(this); + connect(m_tcpSocket, &QTcpSocket::disconnected, this, &DictEngine::socketClosed); + connect(m_tcpSocket, &QTcpSocket::errorOccurred, this, [this] { + Q_EMIT dictErrorOccurred(m_tcpSocket->error(), m_tcpSocket->errorString()); + socketClosed(); + }); + connect(m_tcpSocket, &QTcpSocket::readyRead, this, &DictEngine::getDicts); + m_tcpSocket->connectToHost(m_serverName, 2628); +} + +void DictEngine::requestDefinition(const QString &query) +{ + if (m_tcpSocket) { + m_definitionTimer.stop(); + m_tcpSocket->abort(); // stop if lookup is in progress and new query is requested + // Delete now to fix "Unexpected null receiver" + delete m_tcpSocket; + m_tcpSocket = nullptr; + } + + m_currentWord = query; + + m_tcpSocket = new QTcpSocket(this); + connect(m_tcpSocket, &QTcpSocket::disconnected, this, &DictEngine::socketClosed); + connect(m_tcpSocket, &QTcpSocket::errorOccurred, this, [this] { + Q_EMIT dictErrorOccurred(m_tcpSocket->error(), m_tcpSocket->errorString()); + socketClosed(); + }); + connect(m_tcpSocket, &QTcpSocket::readyRead, this, &DictEngine::getDefinition, Qt::SingleShotConnection); + m_tcpSocket->connectToHost(m_serverName, 2628); +} + +void DictEngine::requestDefinition(const QString &query, const QString &server) +{ + setServer(server); + requestDefinition(query); +} + +void DictEngine::requestDefinition(const QString &query, const QByteArray &dictionary) +{ + setDict(dictionary); + requestDefinition(query); +} + +void DictEngine::requestDefinition(const QString &query, const QString &server, const QByteArray &dictionary) +{ + setServer(server); + setDict(dictionary); + requestDefinition(query); +} + +void DictEngine::slotDefinitionReadyRead() +{ + m_definitionData += m_tcpSocket->readAll(); + + const bool finished = std::any_of(m_definitionResponses.cbegin(), m_definitionResponses.cend(), [this](const QByteArray &code) { + return m_definitionData.contains(code); + }); + + if (finished) { + slotDefinitionReadFinished(); + return; + } + + // Close the socket after 30s inactivity + m_definitionTimer.start(); +} + +void DictEngine::slotDefinitionReadFinished() +{ + m_definitionTimer.stop(); + + const QString html = wnToHtml(m_currentWord, m_definitionData); + Q_EMIT definitionRecieved(html); + + m_tcpSocket->disconnectFromHost(); + socketClosed(); +} + +void DictEngine::socketClosed() +{ + Q_EMIT dictLoadingChanged(false); + + if (m_tcpSocket) { + m_tcpSocket->deleteLater(); + } + m_tcpSocket = nullptr; +} diff --git a/dict/dictengine.h b/dict/dictengine.h new file mode 100644 index 00000000..1a177927 --- /dev/null +++ b/dict/dictengine.h @@ -0,0 +1,97 @@ +/* + SPDX-FileCopyrightText: 2007 Jeff Cooper + SPDX-FileCopyrightText: 2007 Thomas Georgiou + SPDX-FileCopyrightText: 2022 Alexander Lohnau + + SPDX-License-Identifier: LGPL-2.0-only +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include + +/** + * This class evaluates the basic expressions given in the interface. + */ + +class DictEngine : public QObject +{ + Q_OBJECT + +public: + DictEngine(QObject *parent = nullptr); + ~DictEngine() override; + +Q_SIGNALS: + /** + * @param socketError the type of the last socket error + * @param errorString a human-readable description of the last socket error + */ + void dictErrorOccurred(QAbstractSocket::SocketError socketError, const QString &errorString); + + /** + * @param loading @c true if the dict finder is downloading dict list from + * the Internet, @c false otherwise. + */ + void dictLoadingChanged(bool loading); + + void dictsRecieved(const QMap &dicts); + void definitionRecieved(const QString &html); + +public Q_SLOTS: + void requestDicts(); + void requestDefinition(const QString &query); + void requestDefinition(const QString &query, const QString &server); + void requestDefinition(const QString &query, const QByteArray &dictionary); + void requestDefinition(const QString &query, const QString &server, const QByteArray &dictionary); + +private Q_SLOTS: + /** + * Slot to asynchronously handle \readyRead signal emitted + * when receiving new definitions. + */ + void slotDefinitionReadyRead(); + + /** + * Slot to process definition data when any end response + * listed in \m_definitionResponses is received, or to handle + * \QTimer::timeout signal when no new data are received + * from the socket and no end response is received. + */ + void slotDefinitionReadFinished(); + + void socketClosed(); + +private: + void getDefinition(); + void getDicts(); + void setDict(const QByteArray &dict); + void setServer(const QString &server); + + QHash m_dictNameToDictCode; + QTcpSocket *m_tcpSocket = nullptr; + QString m_currentWord; + QByteArrayList m_dictNames; + QString m_serverName; + QMap> m_availableDictsCache; + + /** + * Stores temporarily received definition data + */ + QByteArray m_definitionData; + + /** + * When \QTimer::timeout is emitted, the existing socket will be closed + * and deleted, and will emit \definitionRecieved to stop the loading + * process. + */ + QTimer m_definitionTimer; + + // https://datatracker.ietf.org/doc/html/rfc2229 + const std::array m_definitionResponses; +}; diff --git a/kdeds/CMakeLists.txt b/kdeds/CMakeLists.txt new file mode 100644 index 00000000..be505234 --- /dev/null +++ b/kdeds/CMakeLists.txt @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: 2024 Natalie Clarius +# SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL + +add_subdirectory(kameleon) diff --git a/kdeds/kameleon/CMakeLists.txt b/kdeds/kameleon/CMakeLists.txt new file mode 100644 index 00000000..2cec00fd --- /dev/null +++ b/kdeds/kameleon/CMakeLists.txt @@ -0,0 +1,49 @@ +# SPDX-FileCopyrightText: 2024 Natalie Clarius +# SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL + +kcoreaddons_add_plugin(kameleon + SOURCES + kameleon.cpp + kameleon.h + INSTALL_NAMESPACE + "kf6/kded") + +target_link_libraries(kameleon + KF6::CoreAddons + KF6::I18n + KF6::AuthCore + KF6::ConfigCore + KF6::ConfigGui + KF6::DBusAddons + KF6::WidgetsAddons) + +ecm_qt_declare_logging_category(kameleon + HEADER kameleon_debug.h + IDENTIFIER KAMELEON + CATEGORY_NAME org.kde.kameleon + DESCRIPTION "kded module for accent coloring device leds" + EXPORT KDEPLASMAADDONS + ) + +########### kauth helper ################ +add_executable(kameleonhelper) +target_sources(kameleonhelper PRIVATE kameleonhelper.cpp kameleonhelper.h) + +target_link_libraries(kameleonhelper + Qt6::Core + KF6::AuthCore + KF6::CoreAddons +) + +install(TARGETS kameleonhelper DESTINATION ${KAUTH_HELPER_INSTALL_DIR}) + +kauth_install_helper_files(kameleonhelper org.kde.kameleonhelper root) +kauth_install_actions(org.kde.kameleonhelper kameleonhelper.actions) + +ecm_qt_declare_logging_category(kameleonhelper + HEADER kameleonhelper_debug.h + IDENTIFIER KAMELEONHELPER + CATEGORY_NAME org.kde.kameleonhelper + DESCRIPTION "Helper for kded module for accent coloring device leds" + EXPORT KDEPLASMAADDONS + ) diff --git a/kdeds/kameleon/kameleon.cpp b/kdeds/kameleon/kameleon.cpp new file mode 100644 index 00000000..e680408b --- /dev/null +++ b/kdeds/kameleon/kameleon.cpp @@ -0,0 +1,153 @@ +/* + SPDX-FileCopyrightText: 2024 Natalie Clarius + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#include "kameleon.h" +#include "kameleon_debug.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +K_PLUGIN_CLASS_WITH_JSON(Kameleon, "kameleon.json") + +Kameleon::Kameleon(QObject *parent, const QList &) + : KDEDModule(parent) + , m_config(KSharedConfig::openConfig("kdeglobals")) + , m_configWatcher(KConfigWatcher::create(m_config)) +{ + findRgbLedDevices(); + if (!isSupported()) { + qCInfo(KAMELEON) << "found no RGB LED devices"; + return; + } + + loadConfig(); + connect(m_configWatcher.get(), &KConfigWatcher::configChanged, this, [this]() { + loadConfig(); + }); +} + +void Kameleon::findRgbLedDevices() +{ + QDir ledsDir(LED_SYSFS_PATH); + ledsDir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable); + auto ledDevices = ledsDir.entryList(); + + // Sort to ensure keys light up nicely one next to the other + QCollator coll; + coll.setNumericMode(true); + std::sort(ledDevices.begin(), ledDevices.end(), [&](const QString &s1, const QString &s2) { + return coll.compare(s1, s2) < 0; + }); + + for (const QString &ledDevice : std::as_const(ledDevices)) { + // Get multicolor index (= RGB capability with order of colors) + QFile indexFile(LED_SYSFS_PATH + ledDevice + LED_INDEX_FILE); + if (!QFileInfo(indexFile).exists()) { + // Not a RGB capable device + continue; + } + if (!indexFile.open(QIODevice::ReadOnly)) { + qCWarning(KAMELEON) << "failed to open" << indexFile.fileName() << indexFile.error() << indexFile.errorString(); + continue; + } + QString colorIndexStr = indexFile.readAll().trimmed(); + indexFile.close(); + QString colorIndex = + colorIndexStr.toLower().replace("red", "r").replace("green", "g").replace("blue", "b").replace(" ", ""); // eg "red green blue" -> "rgb" + if (!(colorIndex.length() == 3 && colorIndex.contains("r") && colorIndex.contains("g") && colorIndex.contains("b"))) { + qCWarning(KAMELEON) << "invalid color index" << colorIndexStr << "read from" << LED_INDEX_FILE << "for device" << ledDevice; + continue; + } + + qCInfo(KAMELEON) << "found RGB LED device" << ledDevice; + m_rgbLedDevices.append(ledDevice); + m_deviceRgbIndices.append(colorIndex); + } +} + +bool Kameleon::isSupported() +{ + return !m_rgbLedDevices.isEmpty(); +} + +bool Kameleon::isEnabled() +{ + return m_enabled; +} + +void Kameleon::setEnabled(bool enabled) +{ + if (enabled != m_enabled) { + qCInfo(KAMELEON) << "enabled changed" << enabled; + m_enabled = enabled; + m_config->group("General").writeEntry("DeviceLedsAccentColored", enabled); + m_config->sync(); + + if (enabled) { + applyColor(m_accentColor); + } else { + applyColor(QColor(QColorConstants::White)); + } + } +} + +void Kameleon::loadConfig() +{ + m_enabled = m_config->group("General").readEntry("DeviceLedsAccentColored", true); + + QColor customAccentColor = m_config->group("General").readEntry("AccentColor", QColor::Invalid); + QColor schemeAccentColor = m_config->group("Colors:View").readEntry("ForegroundActive", QColor::Invalid); + QColor activeAccentColor = customAccentColor.isValid() ? customAccentColor + : schemeAccentColor.isValid() ? schemeAccentColor + : QColor(QColorConstants::White); + + if (activeAccentColor != m_accentColor) { + m_accentColor = activeAccentColor; + if (m_enabled) { + applyColor(m_accentColor); + } + } +} + +void Kameleon::applyColor(QColor color) +{ + QStringList colorStrs; + for (const QString &colorIndex : std::as_const(m_deviceRgbIndices)) { + QStringList colorStrList = {QString(), QString(), QString()}; + colorStrList[colorIndex.indexOf("r")] = QString::number(color.red()); + colorStrList[colorIndex.indexOf("g")] = QString::number(color.green()); + colorStrList[colorIndex.indexOf("b")] = QString::number(color.blue()); + QString colorStr = colorStrList.join(" "); + colorStrs.append(colorStr); + } + + KAuth::Action action("org.kde.kameleonhelper.writecolor"); + action.setHelperId("org.kde.kameleonhelper"); + action.addArgument("devices", m_rgbLedDevices); + action.addArgument("colors", colorStrs); + auto *job = action.execute(); + + connect(job, &KAuth::ExecuteJob::result, this, [job, color] { + if (job->error()) { + qCWarning(KAMELEON) << "failed to write color to devices" << job->errorText(); + return; + } + qCInfo(KAMELEON) << "wrote color" << color.name() << "to LED devices"; + }); + job->start(); +} + +#include "kameleon.moc" diff --git a/kdeds/kameleon/kameleon.h b/kdeds/kameleon/kameleon.h new file mode 100644 index 00000000..170ff9f3 --- /dev/null +++ b/kdeds/kameleon/kameleon.h @@ -0,0 +1,51 @@ +/* + SPDX-FileCopyrightText: 2024 Natalie Clarius + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#pragma once + +#include +#include + +#include + +#define LED_SYSFS_PATH "/sys/class/leds/" +#define LED_INDEX_FILE "/multi_index" +#define LED_RGB_FILE "/multi_intensity" + +class Kameleon : public KDEDModule +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.kameleon") +public: + Kameleon(QObject *parent, const QList &); + + /** + * Returns whether there are any RGB cabaple LED devices. + */ + Q_SCRIPTABLE bool isSupported(); + + /** + * Returns whether accent color syncing is enabled. + */ + Q_SCRIPTABLE bool isEnabled(); + + /** + * Enables or disables accent color syncing. + */ + Q_SCRIPTABLE void setEnabled(bool enabled); + +private: + bool m_enabled = true; + QColor m_accentColor = QColor(QColorConstants::White); + + KSharedConfig::Ptr m_config; + KConfigWatcher::Ptr m_configWatcher; + QStringList m_rgbLedDevices; + QStringList m_deviceRgbIndices; + + void loadConfig(); + void findRgbLedDevices(); + void applyColor(QColor color); +}; diff --git a/kdeds/kameleon/kameleon.json b/kdeds/kameleon/kameleon.json new file mode 100644 index 00000000..86ee366e --- /dev/null +++ b/kdeds/kameleon/kameleon.json @@ -0,0 +1,74 @@ +{ + "KPlugin": { + "Description": "Synchronizes device LEDs with the Plasma accent color", + "Description[ar]": "يزامن مصابيح LED الخاصة بالجهاز مع لون البلازما المميز", + "Description[bg]": "Синхронизира светодиодите на устройството с акцентния цвят на Plasma", + "Description[ca@valencia]": "Sincronitza els LED del dispositiu amb el color d'accent de Plasma", + "Description[ca]": "Sincronitza els LED del dispositiu amb el color d'accent del Plasma", + "Description[da]": "Synkronisér enheders LED'er med Plasma-accentfarven", + "Description[de]": "Die Geräte-LEDs mit den Farben von Plasma synchronisieren", + "Description[en_GB]": "Synchronises device LEDs with the Plasma accent colour", + "Description[eo]": "Sinkronigas aparat-LEDojn kun la akcentkoloro de Plasma", + "Description[es]": "Sincroniza los LED del dispositivo con el color de acento de Plasma", + "Description[eu]": "Gailuko LEDak Plasmaren azentu-kolorearekin sinkronizatzen ditu", + "Description[fr]": "Synchronise les diodes lumineuses du périphérique avec la couleur d'accentuation de Plasma", + "Description[gl]": "Sincroniza dispositivos LED coa cor de énfase de Plasma.", + "Description[he]": "מסנכרן את נוריות ההתקן עם הצבע המשני של פלזמה", + "Description[hu]": "Szinkronizálja az eszközök LED-jeit a Plasma kiemelőszínjével", + "Description[ia]": "Synchronisa dispositivo LEDs con le color de accento de Plasma", + "Description[is]": "Samstilla LED-ljós tækis og áherslulit Plasma", + "Description[it]": "Sincronizza i LED del dispositivo con il colore secondario di Plasma", + "Description[ka]": "მოწყობილობის LED-ების სინქრონიზაცია Plasma-ის მახვილის ფერთან", + "Description[ko]": "Plasma 강조색과 장치 LED 동기화", + "Description[lt]": "Sinchronizuoja įrenginio šviesos diodus su Plasma paryškinimo spalva", + "Description[nl]": "LED's van apparaat synchroniseren met de kleur voor accentueren van Plasma", + "Description[nn]": "Synkroniser maskinvare-LED med kontrastfargen til Plasma", + "Description[pl]": "Synchronizuj LEDy urządzenia z barwą akcentującą Plazmy", + "Description[pt_BR]": "Sincroniza os LEDs do dispositivo com a cor de destaque do Plasma", + "Description[ro]": "Sincronizează LED-urile dispozitivului cu culoarea de accent Plasma", + "Description[ru]": "Синхронизация цвета подсветки устройства с цветом выделения Plasma", + "Description[sl]": "Sinhronizira LED diode naprave z barvo poudarka Plasme", + "Description[sv]": "Synkroniserar enhetens LED:ar med Plasmas accentfärg", + "Description[tr]": "Aygıt LED’lerini Plasma vurgu rengiyle eşleştirir", + "Description[uk]": "Синхронізує світлодіоди пристрою із кольором акценту Плазми", + "Description[x-test]": "xxSynchronizes device LEDs with the Plasma accent colorxx", + "Description[zh_CN]": "将 Plasma 的强调色与设备的灯效同步", + "Description[zh_TW]": "讓裝置 LED 同步使用 Plasma 強調色", + "Name": "Kameleon", + "Name[ar]": "كاميليون", + "Name[bg]": "Kameleon", + "Name[ca@valencia]": "Kameleon", + "Name[ca]": "Kameleon", + "Name[cs]": "Kameleon", + "Name[da]": "Kamelæon", + "Name[de]": "Kameleon", + "Name[en_GB]": "Kameleon", + "Name[eo]": "Kameleon", + "Name[es]": "Kameleon", + "Name[eu]": "Kameleon", + "Name[fr]": "Kameleon", + "Name[gl]": "Kameleon", + "Name[he]": "Kameleon", + "Name[hu]": "Kaméleon", + "Name[ia]": "Kameleon", + "Name[is]": "Kameleon", + "Name[it]": "Kameleon", + "Name[ka]": "კამელეონი", + "Name[ko]": "Kameleon", + "Name[lt]": "Kameleon", + "Name[nl]": "Kameleon", + "Name[nn]": "Kameleon", + "Name[pl]": "Kameleon", + "Name[pt_BR]": "Kameleon", + "Name[ro]": "Kameleon", + "Name[ru]": "Kameleon", + "Name[sl]": "Kameleon", + "Name[sv]": "Kameleon", + "Name[tr]": "Kameleon", + "Name[uk]": "Kameleon", + "Name[x-test]": "xxKameleonxx", + "Name[zh_CN]": "变色龙", + "Name[zh_TW]": "Kameleon" + }, + "X-KDE-Kded-autoload": true +} diff --git a/kdeds/kameleon/kameleon.json.license b/kdeds/kameleon/kameleon.json.license new file mode 100644 index 00000000..0b0fa751 --- /dev/null +++ b/kdeds/kameleon/kameleon.json.license @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2024 Natalie Clarius +# SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL diff --git a/kdeds/kameleon/kameleonhelper.actions b/kdeds/kameleon/kameleonhelper.actions new file mode 100644 index 00000000..626dd7cd --- /dev/null +++ b/kdeds/kameleon/kameleonhelper.actions @@ -0,0 +1,114 @@ +# SPDX-FileCopyrightText: 2024 Natalie Clarius +# SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL + +[Domain] +Name=Kameleon +Name[ar]=كاميليون +Name[az]=Xameleon +Name[bg]=Kameleon +Name[ca]=Kameleon +Name[ca@valencia]=Kameleon +Name[cs]=Kameleon +Name[da]=Kamæleon +Name[en_GB]=Kameleon +Name[eo]=Kameleon +Name[es]=Kameleon +Name[eu]=Kameleon +Name[fr]=Kameleon +Name[gl]=Kameleon +Name[he]=Kameleon +Name[hu]=Kaméleon +Name[ia]=Kameleon +Name[is]=Kameljón +Name[it]=Kameleon +Name[ka]=კამელეონი +Name[ko]=Kameleon +Name[lt]=Kameleon +Name[nl]=Kameleon +Name[nn]=Kameleon +Name[pl]=Kameleon +Name[pt_BR]=Kameleon +Name[ro]=Kameleon +Name[ru]=Kameleon +Name[sl]=Kameleon +Name[sv]=Kameleon +Name[tr]=Kameleon +Name[uk]=Kameleon +Name[x-test]=xxKameleonxx +Name[zh_CN]=变色龙 +Name[zh_TW]=Kameleon +Icon=color + +[org.kde.kameleonhelper.writecolor] +Name=Write color +Name[ar]=اكتب لون +Name[az]=Rəngi yazmaq +Name[bg]=Запис на цвят +Name[ca]=Escriu un color +Name[ca@valencia]=Escriu un color +Name[cs]=Bílá barva +Name[da]=Skriv farve +Name[en_GB]=Write colour +Name[eo]=Skribkoloro +Name[es]=Color de escritura +Name[eu]=Idatzi kolorea +Name[fr]=Couleur d'écriture +Name[gl]=Escribir unha cor +Name[he]=כתיבת צבע +Name[hu]=Szín írása +Name[ia]=Scribe color +Name[is]=Skriflitur +Name[it]=Scrivi il colore +Name[ka]=ჩაწერის ფერი +Name[ko]=색상 기록 +Name[lt]=Rašyti spalvą +Name[nl]=Kleur schrijven +Name[nn]=Skriv farge +Name[pl]=Zapisz barwę +Name[pt_BR]=Cor de escrita +Name[ro]=Scrie culoare +Name[ru]=Запись цвета +Name[sl]=Barva pisanja +Name[sv]=Skrivfärg +Name[tr]=Renk yaz +Name[uk]=Записування кольору +Name[x-test]=xxWrite colorxx +Name[zh_CN]=写入颜色 +Name[zh_TW]=寫入顏色 +Description=Allow writing accent color to device LEDs +Description[ar]=اسمح بكتابة لون التمييز إلى مصابيح الجهاز +Description[az]=Vurğu rəngini cihazın LED-lərinə yazılmasına icazə verir +Description[bg]=Разрешаване на запис на акцентен цвят на светодиодите на устройството +Description[ca]=Permet escriure l'accent de color a un dispositiu LED +Description[ca@valencia]=Permet escriure l'accent de color a un dispositiu LED +Description[da]=Tillad at skrive accentfarven til enhedens LEDs +Description[en_GB]=Allow writing accent colour to device LEDs +Description[eo]=Permesi skribadon de akcentkoloro al aparat-LEDoj +Description[es]=Permitir color de acento de escritura a los LED del dispositivo +Description[eu]=Gailuko LEDetan azentu-kolorea idazteko aukera ematen du +Description[fr]=Autoriser l'envoi de la couleur d'accentuation sur les diodes lumineuses du périphérique +Description[gl]=Permitir escribir a cor de énfase en dispositivos LED. +Description[he]=לאפשר כתיבת צבע משני לנוריות ההתקן +Description[hu]=Lehetővé teszi kiemelőszínek írását az eszközök LED-jeire +Description[ia]=Permette scriberaccento de color a dispositivo LEDs +Description[is]=Leyfa ritun áherslulitar í LED-merki tækis +Description[it]=Consenti la scrittura del colore secondario sui LED del dispositivo +Description[ka]=მახვილის ფერების ჩაწერის დაშვება მოწყობილობის LED-ებზე +Description[ko]=장치 LED에 강조색 기록 +Description[lt]=Leisti rašyti paryškinimo spalvą į įrenginio šviesos diodus +Description[nl]=Accentuatiekleur toestaan te schrijven naar LED's van apparaat +Description[nn]=Tillatt skriving av kontrastfargen til LED-ar på tilkopla utstyr +Description[pl]=Zezwól na zapisywanie barwy akcentującej do LEDów urządzenia +Description[pt_BR]=Permitir a escrita de cor de destaque nos LEDs do dispositivo +Description[ro]=Permite scrierea culorilor de accent în LED-urile dispozitivului +Description[ru]=Возможность записи акцентного цвета на светодиоды устройства +Description[sl]=Dovoli pisanje poudarjene barve na LED diode naprave +Description[sv]=Tillåt att skriva accentfärger till enhetens LED:er +Description[tr]=Vurgu rengini aygıt LED’lerine yazmaya olanak tanır +Description[uk]=Уможливлення запису кольору акценту на світлодіоди пристрою +Description[x-test]=xxAllow writing accent color to device LEDsxx +Description[zh_CN]=允许将系统强调色写入到设备灯效 +Description[zh_TW]=提供寫入強調色到裝置 LED 的支援 +Policy=yes +PolicyInactive=yes +Persistence=session diff --git a/kdeds/kameleon/kameleonhelper.cpp b/kdeds/kameleon/kameleonhelper.cpp new file mode 100644 index 00000000..24b1fe9c --- /dev/null +++ b/kdeds/kameleon/kameleonhelper.cpp @@ -0,0 +1,54 @@ +/* + SPDX-FileCopyrightText: 2024 Natalie Clarius + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#include "kameleonhelper.h" +#include "kameleonhelper_debug.h" + +#include + +#include +#include +#include + +KAuth::ActionReply KameleonHelper::writecolor(const QVariantMap &args) +{ + QStringList devices = args.value(QStringLiteral("devices")).toStringList(); + QStringList colors = args.value(QStringLiteral("colors")).toStringList(); + if (devices.length() != colors.length()) { + qCWarning(KAMELEONHELPER) << "lists of devices and colors do not match in length"; + return KAuth::ActionReply::HelperErrorReply(); + } + for (int i = 0; i < devices.length(); ++i) { + QString device = devices.at(i); + if (device.contains(QLatin1String("..")) || device.contains(QLatin1Char('/'))) { + qCWarning(KAMELEONHELPER) << "invalid device name" << device; + return KAuth::ActionReply::HelperErrorReply(); + } + + QByteArray color = colors.at(i).toUtf8(); + if (!(color.length() >= QByteArray("0 0 0").length() && color.length() <= QByteArray("255 255 255").length())) { + qCWarning(KAMELEONHELPER) << "invalid RGB color" << color << "for device" << device; + return KAuth::ActionReply::HelperErrorReply(); + } + + QFile file(LED_SYSFS_PATH + device + LED_RGB_FILE); + if (!file.open(QIODevice::WriteOnly | QIODevice::ExistingOnly)) { + qCWarning(KAMELEONHELPER) << "Opening" << file.fileName() << "failed:" << file.error() << file.errorString(); + return KAuth::ActionReply::HelperErrorReply(); + } + const int bytesWritten = file.write(color); + if (bytesWritten == -1) { + qCWarning(KAMELEONHELPER) << "writing to" << file.fileName() << "failed:" << file.error() << file.errorString(); + return KAuth::ActionReply::HelperErrorReply(); + } + qCDebug(KAMELEONHELPER) << "wrote" << color << "to" << device; + } + + return KAuth::ActionReply::SuccessReply(); +} + +KAUTH_HELPER_MAIN("org.kde.kameleonhelper", KameleonHelper) + +#include "moc_kameleonhelper.cpp" diff --git a/kdeds/kameleon/kameleonhelper.h b/kdeds/kameleon/kameleonhelper.h new file mode 100644 index 00000000..86f68b7a --- /dev/null +++ b/kdeds/kameleon/kameleonhelper.h @@ -0,0 +1,23 @@ +/* + SPDX-FileCopyrightText: 2024 Natalie Clarius + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#ifndef _KAMELEONHELPER_H_ +#define _KAMELEONHELPER_H_ + +#include + +#include + +#define LED_SYSFS_PATH "/sys/class/leds/" +#define LED_RGB_FILE "/multi_intensity" + +class KameleonHelper : public QObject +{ + Q_OBJECT +public Q_SLOTS: + KAuth::ActionReply writecolor(const QVariantMap &args); +}; + +#endif // _KameleonHELPER_H_ diff --git a/kwin/CMakeLists.txt b/kwin/CMakeLists.txt new file mode 100644 index 00000000..42c3372d --- /dev/null +++ b/kwin/CMakeLists.txt @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: 2023 Vlad Zahorodnii +# +# SPDX-License-Identifier: BSD-3-Clause + +add_subdirectory(effects) +add_subdirectory(windowswitchers) diff --git a/kwin/effects/CMakeLists.txt b/kwin/effects/CMakeLists.txt new file mode 100644 index 00000000..65429855 --- /dev/null +++ b/kwin/effects/CMakeLists.txt @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2023 Vlad Zahorodnii +# +# SPDX-License-Identifier: BSD-3-Clause + +add_subdirectory(cube) diff --git a/kwin/effects/cube/CMakeLists.txt b/kwin/effects/cube/CMakeLists.txt new file mode 100644 index 00000000..b1e5ba3c --- /dev/null +++ b/kwin/effects/cube/CMakeLists.txt @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: 2023 Vlad Zahorodnii +# +# SPDX-License-Identifier: BSD-3-Clause + +add_subdirectory(kcm) +kpackage_install_package(package cube effects kwin) diff --git a/kwin/effects/cube/Messages.sh b/kwin/effects/cube/Messages.sh new file mode 100644 index 00000000..bc56cddc --- /dev/null +++ b/kwin/effects/cube/Messages.sh @@ -0,0 +1,3 @@ +#! /usr/bin/env bash +$EXTRACTRC *.ui >> rc.cpp || exit 11 +$XGETTEXT `find . -name \*.cpp -o -name \*.qml` -o $podir/kwin_effect_cube.pot diff --git a/kwin/effects/cube/kcm/CMakeLists.txt b/kwin/effects/cube/kcm/CMakeLists.txt new file mode 100644 index 00000000..d291d78c --- /dev/null +++ b/kwin/effects/cube/kcm/CMakeLists.txt @@ -0,0 +1,25 @@ +# SPDX-FileCopyrightText: 2022 Vlad Zahorodnii +# +# SPDX-License-Identifier: BSD-3-Clause + +set(kwin_cube_config_SOURCES cubeeffectkcm.cpp) +ki18n_wrap_ui(kwin_cube_config_SOURCES cubeeffectkcm.ui) + +kcoreaddons_add_plugin(kwin_cube_config INSTALL_NAMESPACE "kwin/effects/configs" SOURCES ${kwin_cube_config_SOURCES}) +target_sources(kwin_cube_config PRIVATE + resources.qrc +) +target_link_libraries(kwin_cube_config + KF6::ConfigCore + KF6::ConfigGui + KF6::ConfigWidgets + KF6::CoreAddons + KF6::GlobalAccel + KF6::I18n + KF6::KCMUtils + KF6::XmlGui +) + +target_compile_definitions(kwin_cube_config PRIVATE + -DTRANSLATION_DOMAIN=\"kwin_effect_cube\" +) diff --git a/kwin/effects/cube/kcm/cubeeffectkcm.cpp b/kwin/effects/cube/kcm/cubeeffectkcm.cpp new file mode 100644 index 00000000..3cc76382 --- /dev/null +++ b/kwin/effects/cube/kcm/cubeeffectkcm.cpp @@ -0,0 +1,157 @@ +/* + SPDX-FileCopyrightText: 2022 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL +*/ + +#include "cubeeffectkcm.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +K_PLUGIN_CLASS(CubeEffectConfig) + +CubeEffectConfig::CubeEffectConfig(QObject *parent, const KPluginMetaData &data) + : KCModule(parent, data) +{ + ui.setupUi(widget()); + + QFile xmlFile(QStringLiteral(":/main.xml")); + KConfigGroup cg = KSharedConfig::openConfig(QStringLiteral("kwinrc"))->group("Effect-cube"); + m_configLoader = new KConfigLoader(cg, &xmlFile, this); + addConfig(m_configLoader, widget()); + + auto actionCollection = new KActionCollection(this, QStringLiteral("kwin")); + actionCollection->setComponentDisplayName(i18n("KWin")); + actionCollection->setConfigGroup(QStringLiteral("Cube")); + actionCollection->setConfigGlobal(true); + + const QKeySequence defaultToggleShortcut = Qt::META | Qt::Key_C; + QAction *toggleAction = actionCollection->addAction(QStringLiteral("Cube")); + toggleAction->setText(i18n("Toggle Cube")); + toggleAction->setProperty("isConfigurationAction", true); + KGlobalAccel::self()->setDefaultShortcut(toggleAction, {defaultToggleShortcut}); + KGlobalAccel::self()->setShortcut(toggleAction, {defaultToggleShortcut}); + + ui.shortcutsEditor->addCollection(actionCollection); + connect(ui.shortcutsEditor, &KShortcutsEditor::keyChange, this, &CubeEffectConfig::markAsChanged); + + connect(ui.button_SelectSkyBox, &QPushButton::clicked, this, [this]() { + auto dialog = new QFileDialog(widget()); + dialog->setFileMode(QFileDialog::ExistingFile); + connect(dialog, &QFileDialog::fileSelected, ui.kcfg_SkyBox, &QLineEdit::setText); + dialog->open(); + }); + + connect(ui.button_Color, &QPushButton::toggled, this, &CubeEffectConfig::updateBackgroundFromUi); + connect(ui.button_SkyBox, &QPushButton::toggled, this, &CubeEffectConfig::updateBackgroundFromUi); + connect(ui.slider_DistanceFactor, &QSlider::sliderMoved, this, &CubeEffectConfig::updateDistanceFactorFromUi); +} + +void CubeEffectConfig::load() +{ + KCModule::load(); + updateUiFromConfig(); + updateUnmanagedState(); +} + +void CubeEffectConfig::save() +{ + ui.shortcutsEditor->save(); + m_configLoader->save(); + + KCModule::save(); + updateUnmanagedState(); + + QDBusMessage reconfigureMessage = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KWin"), + QStringLiteral("/Effects"), + QStringLiteral("org.kde.kwin.Effects"), + QStringLiteral("reconfigureEffect")); + reconfigureMessage.setArguments({QStringLiteral("cube")}); + QDBusConnection::sessionBus().call(reconfigureMessage); +} + +void CubeEffectConfig::defaults() +{ + KCModule::defaults(); + updateUiFromDefaultConfig(); + updateUnmanagedState(); +} + +void CubeEffectConfig::updateUiFromConfig() +{ + setUiBackground(m_configLoader->findItemByName(QStringLiteral("Background"))->property().toInt()); + setDistanceFactor(m_configLoader->findItemByName(QStringLiteral("DistanceFactor"))->property().toDouble()); +} + +void CubeEffectConfig::updateUiFromDefaultConfig() +{ + setUiBackground(m_configLoader->findItemByName(QStringLiteral("Background"))->property().toInt()); + setDistanceFactor(m_configLoader->findItemByName(QStringLiteral("DistanceFactor"))->property().toDouble()); + ui.shortcutsEditor->allDefault(); +} + +int CubeEffectConfig::uiBackground() const +{ + if (ui.button_SkyBox->isChecked()) { + return 1; + } else { + return 0; + } +} + +void CubeEffectConfig::setUiBackground(int mode) +{ + switch (mode) { + case 1: + ui.button_SkyBox->setChecked(true); + break; + case 0: + default: + ui.button_Color->setChecked(true); + break; + } +} + +void CubeEffectConfig::updateBackgroundFromUi() +{ + m_configLoader->findItemByName(QStringLiteral("Background"))->setProperty(uiBackground()); + updateUnmanagedState(); +} + +qreal CubeEffectConfig::distanceFactor() const +{ + return ui.slider_DistanceFactor->value() / 100.0; +} + +void CubeEffectConfig::setDistanceFactor(qreal factor) +{ + ui.slider_DistanceFactor->setValue(std::round(factor * 100)); +} + +void CubeEffectConfig::updateDistanceFactorFromUi() +{ + m_configLoader->findItemByName(QStringLiteral("DistanceFactor"))->setProperty(distanceFactor()); + updateUnmanagedState(); +} + +void CubeEffectConfig::updateUnmanagedState() +{ + const auto backgroundItem = m_configLoader->findItemByName(QStringLiteral("Background")); + const auto distanceFactorItem = m_configLoader->findItemByName(QStringLiteral("DistanceFactor")); + + unmanagedWidgetChangeState(backgroundItem->isSaveNeeded() || distanceFactorItem->isSaveNeeded()); + unmanagedWidgetDefaultState(backgroundItem->isDefault() || distanceFactorItem->isDefault()); +} + +#include "cubeeffectkcm.moc" diff --git a/kwin/effects/cube/kcm/cubeeffectkcm.h b/kwin/effects/cube/kcm/cubeeffectkcm.h new file mode 100644 index 00000000..253342c8 --- /dev/null +++ b/kwin/effects/cube/kcm/cubeeffectkcm.h @@ -0,0 +1,42 @@ +/* + SPDX-FileCopyrightText: 2022 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL +*/ + +#pragma once + +#include + +#include "ui_cubeeffectkcm.h" + +class KConfigLoader; + +class CubeEffectConfig : public KCModule +{ + Q_OBJECT + +public: + CubeEffectConfig(QObject *parent, const KPluginMetaData &data); + +public Q_SLOTS: + void load() override; + void save() override; + void defaults() override; + +private: + void updateUiFromConfig(); + void updateUiFromDefaultConfig(); + void updateUnmanagedState(); + + int uiBackground() const; + void setUiBackground(int mode); + void updateBackgroundFromUi(); + + qreal distanceFactor() const; + void setDistanceFactor(qreal factor); + void updateDistanceFactorFromUi(); + + ::Ui::CubeEffectConfig ui; + KConfigLoader *m_configLoader = nullptr; +}; diff --git a/kwin/effects/cube/kcm/cubeeffectkcm.ui b/kwin/effects/cube/kcm/cubeeffectkcm.ui new file mode 100644 index 00000000..32370a7c --- /dev/null +++ b/kwin/effects/cube/kcm/cubeeffectkcm.ui @@ -0,0 +1,302 @@ + + + CubeEffectConfig + + + + 0 + 0 + 455 + 361 + + + + + 400 + 250 + + + + + + + Cube face displacement: + + + + + + + + + 300 + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + Less + + + + + + + + 0 + 0 + + + + More + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Distance: + + + + + + + + + 100 + + + 300 + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + Closer + + + + + + + + 0 + 0 + + + + Farther + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Mouse: + + + + + + + Invert X + + + + + + + Invert Y + + + + + + + Background: + + + + + + + + + + + + 0 + 0 + + + + Plain color: + + + + + + + false + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Skybox: + + + + + + + false + + + Type path or URL... + + + + + + + false + + + Select... + + + + + + + + + + + + 0 + 0 + + + + + + + + + KColorButton + QPushButton +
kcolorbutton.h
+
+ + KShortcutsEditor + QWidget +
kshortcutseditor.h
+ 1 +
+
+ + + + button_SkyBox + toggled(bool) + button_SelectSkyBox + setEnabled(bool) + + + 242 + 229 + + + 446 + 235 + + + + + button_SkyBox + toggled(bool) + kcfg_SkyBox + setEnabled(bool) + + + 242 + 229 + + + 356 + 234 + + + + + button_Color + toggled(bool) + kcfg_BackgroundColor + setEnabled(bool) + + + 196 + 175 + + + 292 + 181 + + + + +
diff --git a/kwin/effects/cube/kcm/cubeeffectkcm.ui.license b/kwin/effects/cube/kcm/cubeeffectkcm.ui.license new file mode 100644 index 00000000..cf53d2bc --- /dev/null +++ b/kwin/effects/cube/kcm/cubeeffectkcm.ui.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: None +SPDX-License-Identifier: CC0-1.0 diff --git a/kwin/effects/cube/kcm/resources.qrc b/kwin/effects/cube/kcm/resources.qrc new file mode 100644 index 00000000..095ba8c6 --- /dev/null +++ b/kwin/effects/cube/kcm/resources.qrc @@ -0,0 +1,5 @@ + + + ../package/contents/config/main.xml + + diff --git a/kwin/effects/cube/kcm/resources.qrc.license b/kwin/effects/cube/kcm/resources.qrc.license new file mode 100644 index 00000000..cf53d2bc --- /dev/null +++ b/kwin/effects/cube/kcm/resources.qrc.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: None +SPDX-License-Identifier: CC0-1.0 diff --git a/kwin/effects/cube/package/contents/config/main.xml b/kwin/effects/cube/package/contents/config/main.xml new file mode 100644 index 00000000..8772e60f --- /dev/null +++ b/kwin/effects/cube/package/contents/config/main.xml @@ -0,0 +1,34 @@ + + + + + + 100 + + + 1.75 + + + + + false + + + false + + + + Color + + + + + + + #212427 + + + diff --git a/kwin/effects/cube/package/contents/ui/Cube.qml b/kwin/effects/cube/package/contents/ui/Cube.qml new file mode 100644 index 00000000..9583e407 --- /dev/null +++ b/kwin/effects/cube/package/contents/ui/Cube.qml @@ -0,0 +1,45 @@ +/* + SPDX-FileCopyrightText: 2022 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL +*/ + +import QtQuick +import QtQuick3D +import org.kde.kwin as KWinComponents + +Node { + id: cube + + property real faceDisplacement: 100 + required property size faceSize + readonly property real faceDistance: 0.5 * faceSize.width / Math.tan(angleTick * Math.PI / 360) + faceDisplacement; + readonly property real angleTick: 360 / faceRepeater.count + + function desktopAt(azimuth) { + let index = Math.round(azimuth / angleTick) % faceRepeater.count; + if (index < 0) { + index += faceRepeater.count; + } + return faceRepeater.objectAt(index).desktop; + } + + function desktopAzimuth(desktop) { + return cube.angleTick * (desktop.x11DesktopNumber - 1); + } + + Repeater3D { + id: faceRepeater + model: KWinComponents.VirtualDesktopModel {} + delegate: CubeFace { + faceSize: cube.faceSize + scale: Qt.vector3d(faceSize.width / 100, faceSize.height / 100, 1) + eulerRotation.y: cube.angleTick * index + position: { + const transform = Qt.matrix4x4(); + transform.rotate(cube.angleTick * index, Qt.vector3d(0, 1, 0)); + return transform.times(Qt.vector3d(0, 0, cube.faceDistance)); + } + } + } +} diff --git a/kwin/effects/cube/package/contents/ui/CubeCameraController.qml b/kwin/effects/cube/package/contents/ui/CubeCameraController.qml new file mode 100644 index 00000000..65faf018 --- /dev/null +++ b/kwin/effects/cube/package/contents/ui/CubeCameraController.qml @@ -0,0 +1,135 @@ +/* + SPDX-FileCopyrightText: 2022 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL +*/ + +import QtQuick +import QtQuick3D + +Item { + id: root + + readonly property bool busy: status.useMouse + + required property Camera camera + + property quaternion rotation: Quaternion.fromEulerAngles(0, 0, 0) + property real radius: 2000 + + property real xSpeed: 10 + property real ySpeed: 10 + + property bool xInvert: false + property bool yInvert: false + + implicitWidth: parent.width + implicitHeight: parent.height + + onRotationChanged: root.updateCamera(); + onRadiusChanged: root.updateCamera(); + + DragHandler { + id: dragHandler + target: null + acceptedModifiers: Qt.NoModifier + onCentroidChanged: { + mouseMoved(Qt.vector2d(centroid.position.x, centroid.position.y), false); + } + + onActiveChanged: { + if (active) { + mousePressed(Qt.vector2d(centroid.position.x, centroid.position.y)); + } else { + mouseReleased(Qt.vector2d(centroid.position.x, centroid.position.y)); + } + } + } + + WheelHandler { + id: wheelHandler + orientation: Qt.Vertical + target: null + onWheel: event => { + let delta = (event.inverted ? -1 : 1) * event.angleDelta.y * 0.01; + root.radius += root.radius * 0.1 * delta + } + } + + TapHandler { + onTapped: root.forceActiveFocus() + } + + function mousePressed(newPos) { + root.forceActiveFocus() + status.currentPos = newPos + status.lastPos = newPos + status.useMouse = true; + } + + function mouseReleased(newPos) { + status.useMouse = false; + } + + function mouseMoved(newPos: vector2d) { + status.currentPos = newPos; + } + + function updateCamera() { + const eulerRotation = root.rotation.toEulerAngles(); + const theta = (eulerRotation.x + 90) * Math.PI / 180; + const phi = eulerRotation.y * Math.PI / 180; + + camera.position = Qt.vector3d(radius * Math.sin(phi) * Math.sin(theta), + radius * Math.cos(theta), + radius * Math.cos(phi) * Math.sin(theta)); + camera.rotation = root.rotation; + } + + FrameAnimation { + running: root.busy + onTriggered: status.processInput(frameTime); + } + + QtObject { + id: status + + property bool useMouse: false + + property real minElevation: -30 + property real maxElevation: 30 + + property vector2d lastPos: Qt.vector2d(0, 0) + property vector2d currentPos: Qt.vector2d(0, 0) + + function processInput(dt) { + if (useMouse) { + const eulerRotation = root.rotation.toEulerAngles(); + + const pixelDelta = Qt.vector2d(lastPos.x - currentPos.x, + lastPos.y - currentPos.y); + lastPos = currentPos; + + let azimuthDelta = pixelDelta.x * xSpeed * dt + if (xInvert) { + azimuthDelta = -azimuthDelta; + } + let azimuth = (eulerRotation.y + azimuthDelta) % 360; + + let elevationDelta = pixelDelta.y * ySpeed * dt + if (yInvert) { + elevationDelta = -elevationDelta; + } + + let elevation = eulerRotation.x + elevationDelta; + if (elevation < minElevation) { + elevation = minElevation; + } else if (elevation > maxElevation) { + elevation = maxElevation; + } + + root.rotation = Quaternion.fromEulerAngles(elevation, azimuth, 0); + } + } + } +} diff --git a/kwin/effects/cube/package/contents/ui/CubeFace.qml b/kwin/effects/cube/package/contents/ui/CubeFace.qml new file mode 100644 index 00000000..6869657f --- /dev/null +++ b/kwin/effects/cube/package/contents/ui/CubeFace.qml @@ -0,0 +1,32 @@ +/* + SPDX-FileCopyrightText: 2022 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL +*/ + +import QtQuick +import QtQuick3D + +Model { + id: face + + required property QtObject desktop + required property int index + required property size faceSize + + pickable: true + source: "#Rectangle" + materials: [ + DefaultMaterial { + cullMode: Material.NoCulling + lighting: DefaultMaterial.NoLighting + diffuseMap: Texture { + sourceItem: DesktopView { + desktop: face.desktop + width: faceSize.width + height: faceSize.height + } + } + } + ] +} diff --git a/kwin/effects/cube/package/contents/ui/DesktopView.qml b/kwin/effects/cube/package/contents/ui/DesktopView.qml new file mode 100644 index 00000000..4f4946b7 --- /dev/null +++ b/kwin/effects/cube/package/contents/ui/DesktopView.qml @@ -0,0 +1,31 @@ +/* + SPDX-FileCopyrightText: 2022 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL +*/ + +import QtQuick +import org.kde.kwin as KWinComponents + +Item { + id: desktopView + + required property QtObject desktop + + Repeater { + model: KWinComponents.WindowFilterModel { + activity: KWinComponents.Workspace.currentActivity + desktop: desktopView.desktop + screenName: targetScreen.name + windowModel: KWinComponents.WindowModel {} + } + + KWinComponents.WindowThumbnail { + wId: model.window.internalId + x: model.window.x - targetScreen.geometry.x + y: model.window.y - targetScreen.geometry.y + z: model.window.stackingOrder + visible: !model.window.minimized + } + } +} diff --git a/kwin/effects/cube/package/contents/ui/PlaceholderView.qml b/kwin/effects/cube/package/contents/ui/PlaceholderView.qml new file mode 100644 index 00000000..48584b1f --- /dev/null +++ b/kwin/effects/cube/package/contents/ui/PlaceholderView.qml @@ -0,0 +1,39 @@ +/* + SPDX-FileCopyrightText: 2024 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL +*/ + +import QtQuick +import QtQuick.Controls +import org.kde.kirigami as Kirigami +import org.kde.kwin as KWinComponents +import org.kde.plasma.components as PlasmaComponents +import org.kde.plasma.extras as PlasmaExtras + +Rectangle { + color: Kirigami.Theme.backgroundColor + focus: true + + TapHandler { + onTapped: effect.deactivate(); + } + + PlasmaExtras.PlaceholderMessage { + anchors.centerIn: parent + width: parent.width - Kirigami.Units.gridUnit * 2 + iconName: "virtual-desktops" + text: i18ndcp("kwin_effect_cube", + "@info:placeholder", + "At least 3 virtual desktops are required to display the Cube, but only %1 is present", + "At least 3 virtual desktops are required to display the Cube, but only %1 are present", + KWinComponents.Workspace.desktops.length) + helpfulAction: Action { + text: i18ndc("kwin_effect_cube", "@action:button", "Add Virtual Desktop") + icon.name: "list-add-symbolic" + onTriggered: KWinComponents.Workspace.createDesktop(KWinComponents.Workspace.desktops.length, "") + } + } + + Keys.onEscapePressed: effect.deactivate(); +} diff --git a/kwin/effects/cube/package/contents/ui/ScreenView.qml b/kwin/effects/cube/package/contents/ui/ScreenView.qml new file mode 100644 index 00000000..a07bfb89 --- /dev/null +++ b/kwin/effects/cube/package/contents/ui/ScreenView.qml @@ -0,0 +1,167 @@ +/* + SPDX-FileCopyrightText: 2022 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL +*/ + +import QtQuick +import QtQuick.Window +import QtQuick3D +import org.kde.kwin as KWinComponents + +import "constants.js" as Constants + +Item { + id: root + focus: true + + readonly property QtObject targetScreen: KWinComponents.SceneView.screen + + function switchTo(desktop) { + KWinComponents.Workspace.currentDesktop = desktop; + effect.deactivate(); + } + + function switchToSelected() { + const eulerRotation = cameraController.rotation.toEulerAngles(); + switchTo(cube.desktopAt(eulerRotation.y)); + } + + View3D { + id: view + anchors.fill: parent + renderMode: View3D.Underlay + + Loader { + id: transparentSceneEnvironment + active: effect.configuration.Background == Constants.Background.Color + sourceComponent: SceneEnvironment { + backgroundMode: SceneEnvironment.Transparent + + // When using View3D.Underlay, SceneEnvironment.clearColor will do nothing. + Binding { + target: root.Window.window + property: "color" + value: effect.configuration.BackgroundColor + } + } + } + + Loader { + id: skyboxSceneEnvironment + active: effect.configuration.Background == Constants.Background.SkyBox + sourceComponent: SceneEnvironment { + backgroundMode: SceneEnvironment.SkyBox + lightProbe: Texture { + source: effect.configuration.SkyBox + } + } + } + + environment: { + if (skyboxSceneEnvironment.active) { + return skyboxSceneEnvironment.item; + } else { + return transparentSceneEnvironment.item; + } + } + + PerspectiveCamera { + id: camera + clipNear: 10.0 + clipFar: 100000.0 + } + + Cube { + id: cube + faceDisplacement: effect.configuration.CubeFaceDisplacement + faceSize: Qt.size(root.width, root.height) + } + + CubeCameraController { + id: cameraController + anchors.fill: parent + state: effect.activated ? "distant" : "close" + camera: camera + xInvert: effect.configuration.MouseInvertedX + yInvert: effect.configuration.MouseInvertedY + + states: [ + State { + name: "close" + PropertyChanges { + target: cameraController + radius: cube.faceDistance + 0.5 * cube.faceSize.height / Math.tan(0.5 * camera.fieldOfView * Math.PI / 180) + rotation: Quaternion.fromEulerAngles(0, cube.desktopAzimuth(KWinComponents.Workspace.currentDesktop), 0) + } + }, + State { + name: "distant" + PropertyChanges { + target: cameraController + radius: cube.faceDistance * effect.configuration.DistanceFactor + 0.5 * cube.faceSize.height / Math.tan(0.5 * camera.fieldOfView * Math.PI / 180) + rotation: Quaternion.fromEulerAngles(0, cube.desktopAzimuth(KWinComponents.Workspace.currentDesktop), 0).times(Quaternion.fromEulerAngles(-20, 0, 0)) + } + } + ] + + Behavior on rotation { + enabled: !cameraController.busy + QuaternionAnimation { + id: rotationAnimation + duration: effect.animationDuration + easing.type: Easing.OutCubic + } + } + Behavior on radius { + NumberAnimation { + duration: effect.animationDuration + easing.type: Easing.OutCubic + } + } + + function rotateToLeft() { + if (rotationAnimation.running) { + return; + } + const eulerAngles = rotation.toEulerAngles(); + let next = Math.floor(eulerAngles.y / cube.angleTick) * cube.angleTick; + if (Math.abs(next - eulerAngles.y) < 0.05 * cube.angleTick) { + next -= cube.angleTick; + } + rotation = Quaternion.fromEulerAngles(0, next - eulerAngles.y, 0).times(rotation); + } + + function rotateToRight() { + if (rotationAnimation.running) { + return; + } + const eulerAngles = rotation.toEulerAngles(); + let next = Math.ceil(eulerAngles.y / cube.angleTick) * cube.angleTick; + if (Math.abs(next - eulerAngles.y) < 0.05 * cube.angleTick) { + next += cube.angleTick; + } + rotation = Quaternion.fromEulerAngles(0, next - eulerAngles.y, 0).times(rotation); + } + } + } + + MouseArea { + anchors.fill: view + onClicked: mouse => { + const hitResult = view.pick(mouse.x, mouse.y); + if (hitResult.objectHit) { + root.switchTo(hitResult.objectHit.desktop); + } else { + root.switchToSelected(); + } + } + } + + Keys.onEscapePressed: effect.deactivate(); + Keys.onLeftPressed: cameraController.rotateToLeft(); + Keys.onRightPressed: cameraController.rotateToRight(); + Keys.onEnterPressed: root.switchToSelected(); + Keys.onReturnPressed: root.switchToSelected(); + Keys.onSpacePressed: root.switchToSelected(); +} diff --git a/kwin/effects/cube/package/contents/ui/constants.js b/kwin/effects/cube/package/contents/ui/constants.js new file mode 100644 index 00000000..d1eb1167 --- /dev/null +++ b/kwin/effects/cube/package/contents/ui/constants.js @@ -0,0 +1,11 @@ +/* + SPDX-FileCopyrightText: 2023 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL +*/ + +// Matches the Background enum in config/main.xml +const Background = { + Color: 0, + SkyBox: 1, +}; diff --git a/kwin/effects/cube/package/contents/ui/main.qml b/kwin/effects/cube/package/contents/ui/main.qml new file mode 100644 index 00000000..6b253118 --- /dev/null +++ b/kwin/effects/cube/package/contents/ui/main.qml @@ -0,0 +1,74 @@ +/* + SPDX-FileCopyrightText: 2022 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL +*/ + +import QtQuick +import org.kde.kirigami as Kirigami +import org.kde.kwin as KWinComponents + +KWinComponents.SceneEffect { + id: effect + + property bool activated: false + readonly property int animationDuration: Kirigami.Units.longDuration + + // stored as a property to keep the component alive and cleanup afterwards + property var mainDelegate: KWinComponents.Workspace.desktops.length < 3 ? Qt.createComponent("PlaceholderView.qml") : Qt.createComponent("ScreenView.qml") + delegate: mainDelegate + Instantiator { + model: effect.configuration.BorderActivate + KWinComponents.ScreenEdgeHandler { + mode: KWinComponents.ScreenEdgeHandler.Pointer + edge: modelData + onActivated: toggle(); + } + } + + Instantiator { + model: effect.configuration.TouchBorderActivate + KWinComponents.ScreenEdgeHandler { + mode: KWinComponents.ScreenEdgeHandler.Touch + edge: modelData + onActivated: toggle(); + } + } + + KWinComponents.ShortcutHandler { + name: "Cube" + text: i18nd("kwin_effect_cube", "Toggle Cube") + sequence: "Meta+C" + onActivated: toggle(); + } + + Timer { + id: deactivateTimer + interval: effect.animationDuration + onTriggered: effect.visible = false + } + + function toggle() { + if (activated) { + deactivate(); + } else { + activate(); + } + } + + function activate() { + if (activated || deactivateTimer.running) { + return; + } + visible = true; + activated = true; + } + + function deactivate() { + if (!activated) { + return; + } + activated = false; + deactivateTimer.start(); + } +} diff --git a/kwin/effects/cube/package/metadata.json b/kwin/effects/cube/package/metadata.json new file mode 100644 index 00000000..df0edbbc --- /dev/null +++ b/kwin/effects/cube/package/metadata.json @@ -0,0 +1,142 @@ +{ + "KPackageStructure": "KWin/Effect", + "KPlugin": { + "Authors": [ + { + "Email": "vlad.zahorodnii@kde.org", + "Name": "Vlad Zahorodnii", + "Name[ar]": "فلاد زاهورودني", + "Name[az]": "Vlad Zahorodnii", + "Name[bg]": "Vlad Zahorodnii", + "Name[ca@valencia]": "Vlad Zahorodnii", + "Name[ca]": "Vlad Zahorodnii", + "Name[cs]": "Vlad Zahorodnii", + "Name[da]": "Vlad Zahorodnii", + "Name[de]": "Vlad Zahorodnii", + "Name[en_GB]": "Vlad Zahorodnii", + "Name[eo]": "Vlad Zahorodnii", + "Name[es]": "Vlad Zahorodnii", + "Name[eu]": "Vlad Zahorodnii", + "Name[fi]": "Vlad Zahorodnii", + "Name[fr]": "Vlad Zahorodnii", + "Name[gl]": "Vlad Zahorodnii", + "Name[he]": "ולאד זהורודני", + "Name[hu]": "Vlad Zahorodnii", + "Name[ia]": "Vlad Zahorodnii", + "Name[id]": "Vlad Zahorodnii", + "Name[is]": "Vlad Zahorodnii", + "Name[it]": "Vlad Zahorodnii", + "Name[ja]": "Vlad Zahorodnii", + "Name[ka]": "Vlad Zahorodnii", + "Name[ko]": "Vlad Zahorodnii", + "Name[lt]": "Vlad Zahorodnii", + "Name[lv]": "Vlad Zahorodnii", + "Name[nl]": "Vlad Zahorodnii", + "Name[nn]": "Vlad Zahorodnii", + "Name[pl]": "Vlad Zahorodnii", + "Name[pt]": "Vlad Zahorodnii", + "Name[pt_BR]": "Vlad Zahorodnii", + "Name[ro]": "Vlad Zahorodnii", + "Name[ru]": "Влад Загородний", + "Name[sk]": "Vlad Zahorodnii", + "Name[sl]": "Vlad Zahorodnii", + "Name[sv]": "Vlad Zahorodnii", + "Name[tr]": "Vlad Zahorodnii", + "Name[uk]": "Влад Загородній", + "Name[vi]": "Vlad Zahorodnii", + "Name[x-test]": "xxVlad Zahorodniixx", + "Name[zh_CN]": "Vlad Zahorodnii", + "Name[zh_TW]": "Vlad Zahorodnii" + } + ], + "BugReportUrl": "https://bugs.kde.org/enter_bug.cgi?product=kdeplasma-addons&component=Cube", + "Category": "Window Management", + "Description": "Arrange desktops in a virtual cube", + "Description[ar]": "رتب أسطح المكتب في مكعب خيالي", + "Description[bg]": "Подреждане на работните площи във виртуален куб", + "Description[ca@valencia]": "Organitza els escriptoris en un cub virtual", + "Description[ca]": "Organitza els escriptoris en un cub virtual", + "Description[cs]": "Uspořádejte pracovní plochy do virtuální kostky", + "Description[da]": "Arrangér skriveborde i en virtuel terning", + "Description[de]": "Die Arbeitsflächen in einem virtuellen Würfel anordnen", + "Description[en_GB]": "Arrange desktops in a virtual cube", + "Description[eo]": "Aranĝi labortablojn en virtuala kubo", + "Description[es]": "Distribuir los escritorios en un cubo virtual", + "Description[eu]": "Antolatu mahaigainak alegiazko kubo batean", + "Description[fi]": "Asettelee työpöydät virtuaaliseksi kuutioksi", + "Description[fr]": "Disposer les bureaux dans un cube virtuel", + "Description[gl]": "Organizar os escritorios nun cubo virtual.", + "Description[he]": "סידור שולחנות עבודה בקובייה וירטואלית", + "Description[hu]": "Asztalok elrendezése egy virtuális kockán", + "Description[ia]": "Dispone scriptorios in un cubo virtual", + "Description[is]": "Raða skjáborðum í sýndartening", + "Description[it]": "Disponi i desktop in un cubo virtuale", + "Description[ja]": "仮想の立方体でデスクトップを管理", + "Description[ka]": "სამუშაო მაგიდებს მოწყობა ვირტუალურ კუბში", + "Description[ko]": "가상 큐브 형태로 바탕 화면 표시", + "Description[lt]": "Išdėstyti darbalaukius virtualiu kubu", + "Description[lv]": "Izkārtojiet darbvirsmas virtuālā kubā", + "Description[nl]": "Bureaubladen arrangeren in en virtuele kubus", + "Description[nn]": "Vis skriveborda som sider på ein kube", + "Description[pl]": "Rozmieść pulpity na wirtualnej kostce", + "Description[pt_BR]": "Organizar desktops em um cubo virtual", + "Description[ro]": "Aranjează birourile într-un cub virtual", + "Description[ru]": "Виртуальный куб для упорядочивания рабочих столов", + "Description[sk]": "Zoradiť plochy vo virtuálnej kocke", + "Description[sl]": "Razpostavi namizja v navidezno kocko", + "Description[sv]": "Arrangera skrivbord i en virtuell kub", + "Description[tr]": "Masaüstlerini sanal bir küp biçiminde düzenleyin", + "Description[uk]": "Упорядкування стільниць у віртуальний куб", + "Description[vi]": "Sắp xếp các bàn làm việc trên một khối hộp ảo", + "Description[x-test]": "xxArrange desktops in a virtual cubexx", + "Description[zh_CN]": "在虚拟立方体中安排桌面", + "Description[zh_TW]": "將各虛擬桌面排列成一個立體方塊", + "EnabledByDefault": false, + "Id": "cube", + "License": "GPL", + "Name": "Cube", + "Name[ar]": "مكعب", + "Name[bg]": "Куб", + "Name[ca@valencia]": "Cub", + "Name[ca]": "Cub", + "Name[cs]": "Kostka", + "Name[da]": "Terning", + "Name[de]": "Würfel", + "Name[en_GB]": "Cube", + "Name[eo]": "Kubo", + "Name[es]": "Cubo", + "Name[eu]": "Kuboa", + "Name[fi]": "Kuutio", + "Name[fr]": "Cube", + "Name[gl]": "Cubo", + "Name[he]": "קובייה", + "Name[hu]": "Kocka", + "Name[ia]": "Cubo", + "Name[is]": "Teningur", + "Name[it]": "Cubo", + "Name[ja]": "デスクトップキューブ", + "Name[ka]": "კუბი", + "Name[ko]": "큐브", + "Name[lt]": "Kubas", + "Name[lv]": "Kubs", + "Name[nl]": "Kubus", + "Name[nn]": "Kube", + "Name[pl]": "Sześcian", + "Name[pt_BR]": "Cubo", + "Name[ro]": "Cub", + "Name[ru]": "Куб", + "Name[sk]": "Kocka", + "Name[sl]": "Kocka", + "Name[sv]": "Kub", + "Name[tr]": "Küp", + "Name[uk]": "Куб", + "Name[vi]": "Khối hộp", + "Name[x-test]": "xxCubexx", + "Name[zh_CN]": "立方体", + "Name[zh_TW]": "立體方塊" + }, + "X-KDE-ConfigModule": "kwin_cube_config", + "X-KDE-Ordering": 60, + "X-KWin-Border-Activate": true, + "X-Plasma-API": "declarativescript" +} diff --git a/kwin/windowswitchers/CMakeLists.txt b/kwin/windowswitchers/CMakeLists.txt new file mode 100644 index 00000000..9872771f --- /dev/null +++ b/kwin/windowswitchers/CMakeLists.txt @@ -0,0 +1,7 @@ +# packages +set(TABBOX_DIR ${KDE_INSTALL_DATADIR}/kwin/tabbox) +install(DIRECTORY big_icons DESTINATION ${TABBOX_DIR}) +install(DIRECTORY compact DESTINATION ${TABBOX_DIR}) +install(DIRECTORY coverswitch DESTINATION ${TABBOX_DIR}) +install(DIRECTORY flipswitch DESTINATION ${TABBOX_DIR}) +install(DIRECTORY sidebar DESTINATION ${TABBOX_DIR}) diff --git a/kwin/windowswitchers/big_icons/contents/ui/main.qml b/kwin/windowswitchers/big_icons/contents/ui/main.qml new file mode 100644 index 00000000..b0a4f912 --- /dev/null +++ b/kwin/windowswitchers/big_icons/contents/ui/main.qml @@ -0,0 +1,137 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2011 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later + */ +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import org.kde.plasma.core as PlasmaCore +import org.kde.kirigami 2.20 as Kirigami +import org.kde.ksvg 1.0 as KSvg +import org.kde.plasma.components 3.0 as PlasmaComponents3 +import org.kde.kwin 3.0 as KWin + +KWin.TabBoxSwitcher { + id: tabBox + + currentIndex: icons.currentIndex + + PlasmaCore.Dialog { + location: PlasmaCore.Types.Floating + visible: tabBox.visible + flags: Qt.X11BypassWindowManagerHint + x: tabBox.screenGeometry.x + tabBox.screenGeometry.width * 0.5 - dialogMainItem.width * 0.5 + y: tabBox.screenGeometry.y + tabBox.screenGeometry.height * 0.5 - dialogMainItem.height * 0.5 + + mainItem: ColumnLayout { + id: dialogMainItem + spacing: Kirigami.Units.smallSpacing * 2 + + width: Math.min(Math.max(tabBox.screenGeometry.width * 0.3, icons.implicitWidth), tabBox.screenGeometry.width * 0.9) + + ListView { + id: icons + + readonly property int iconSize: Kirigami.Units.iconSizes.enormous + readonly property int delegateWidth: iconSize + (highlightItem ? highlightItem.margins.left + highlightItem.margins.right : 0) + readonly property int delegateHeight: iconSize + (highlightItem ? highlightItem.margins.top + highlightItem.margins.bottom : 0) + + Layout.alignment: Qt.AlignHCenter + Layout.maximumWidth: tabBox.screenGeometry.width * 0.9 + + implicitWidth: contentWidth || delegateWidth * 4 + implicitHeight: delegateHeight + + focus: true + orientation: ListView.Horizontal + + model: tabBox.model + delegate: Kirigami.Icon { + property string caption: model.caption + + width: icons.delegateWidth + height: icons.delegateHeight + + source: model.icon + active: index == icons.currentIndex + + TapHandler { + onSingleTapped: { + if (index === icons.currentIndex) { + icons.model.activate(index); + return; + } + icons.currentIndex = index; + } + onDoubleTapped: icons.model.activate(index) + } + } + + highlight: KSvg.FrameSvgItem { + id: highlightItem + imagePath: "widgets/viewitem" + prefix: "hover" + width: icons.iconSize + margins.left + margins.right + height: icons.iconSize + margins.top + margins.bottom + } + + Kirigami.PlaceholderMessage { + anchors.centerIn: parent + anchors.verticalCenterOffset: Math.round(captionLabel.height / 2) + width: parent.width - Kirigami.Units.largeSpacing * 2 + icon.source: "edit-none" + text: i18ndc("kwin", "@info:placeholder no entries in the task switcher", "No open windows") + visible: icons.count === 0 + } + + highlightMoveDuration: 0 + highlightResizeDuration: 0 + boundsBehavior: Flickable.StopAtBounds + } + + PlasmaComponents3.Label { + id: captionLabel + text: icons.currentItem ? icons.currentItem.caption : "" + textFormat: Text.PlainText + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + elide: Text.ElideMiddle + font.weight: Font.Bold + Layout.fillWidth: true + Layout.topMargin: Kirigami.Units.smallSpacing + Layout.bottomMargin: Kirigami.Units.smallSpacing + } + + Connections { + target: tabBox + function onCurrentIndexChanged() { + icons.currentIndex = tabBox.currentIndex; + } + } + + /* + * Key navigation on outer item for two reasons: + * @li we have to emit the change signal + * @li on multiple invocation it does not work on the list view. Focus seems to be lost. + **/ + Keys.onPressed: { + if (event.key == Qt.Key_Up || event.key == Qt.Key_Left) { + if (icons.currentIndex == 0){ + icons.currentIndex = icons.count-1 + } else { + icons.decrementCurrentIndex() + } + } else if (event.key == Qt.Key_Down || event.key == Qt.Key_Right) { + if (icons.currentIndex == icons.count-1){ + icons.currentIndex = 0 + } else { + icons.incrementCurrentIndex() + } + } + } + } + } +} diff --git a/kwin/windowswitchers/big_icons/metadata.json b/kwin/windowswitchers/big_icons/metadata.json new file mode 100644 index 00000000..e8a9e2a7 --- /dev/null +++ b/kwin/windowswitchers/big_icons/metadata.json @@ -0,0 +1,142 @@ +{ + "KPackageStructure": "KWin/WindowSwitcher", + "KPlugin": { + "Authors": [ + { + "Email": "mgraesslin@kde.org", + "Name": "Martin Gräßlin", + "Name[ar]": "مارتن جراجلين", + "Name[az]": "Martin Gräßlin", + "Name[bg]": "Martin Gräßlin", + "Name[ca@valencia]": "Martin Gräßlin", + "Name[ca]": "Martin Gräßlin", + "Name[cs]": "Martin Gräßlin", + "Name[da]": "Martin Gräßlin", + "Name[de]": "Martin Gräßlin", + "Name[en_GB]": "Martin Gräßlin", + "Name[eo]": "Martin Gräßlin", + "Name[es]": "Martin Gräßlin", + "Name[eu]": "Martin Gräßlin", + "Name[fi]": "Martin Gräßlin", + "Name[fr]": "Martin Gräßlin", + "Name[gl]": "Martin Gräßlin", + "Name[he]": "מרטין גרייסלין", + "Name[hu]": "Martin Gräßlin", + "Name[ia]": "Martin Gräßlin", + "Name[id]": "Martin Gräßlin", + "Name[is]": "Martin Gräßlin", + "Name[it]": "Martin Gräßlin", + "Name[ja]": "Martin Gräßlin", + "Name[ka]": "მარტინ გრესსლინი", + "Name[ko]": "Martin Gräßlin", + "Name[lt]": "Martin Gräßlin", + "Name[lv]": "Martin Gräßlin", + "Name[nl]": "Martin Gräßlin", + "Name[nn]": "Martin Gräßlin", + "Name[pl]": "Martin Gräßlin", + "Name[pt]": "Martin Gräßlin", + "Name[pt_BR]": "Martin Gräßlin", + "Name[ro]": "Martin Gräßlin", + "Name[ru]": "Martin Gräßlin", + "Name[sk]": "Martin Gräßlin", + "Name[sl]": "Martin Gräßlin", + "Name[sv]": "Martin Gräßlin", + "Name[tr]": "Martin Gräßlin", + "Name[uk]": "Martin Gräßlin", + "Name[vi]": "Martin Gräßlin", + "Name[x-test]": "xxMartin Gräßlinxx", + "Name[zh_CN]": "Martin Gräßlin", + "Name[zh_TW]": "Martin Gräßlin" + } + ], + "Description": "A window switcher layout using large icons to represent the window", + "Description[ar]": "مخطط مبدل النوافذ باستخدام أيقونات كبيرة لتمثيل النافذة", + "Description[az]": "Pəncərələri göstərmək üçün böyük nişanlarla istifadə edilən pəncərə dəyişirici maketi", + "Description[bg]": "Оформление на програмата за превключване на прозорци с използване на големи икони за представяне на прозорците", + "Description[ca@valencia]": "Una disposició del commutador de finestres que utilitza icones grans per a representar la finestra", + "Description[ca]": "Una disposició del commutador de finestres que usa icones grans per a representar la finestra", + "Description[cs]": "Rozvržení přepínače oken používající velké ikony", + "Description[da]": "Et vindueskifterlayout, der bruger store ikoner til at repræsentere vinduet", + "Description[de]": "Ein Fensterwechsler-Layout, das große Symbole zur Darstellung der Fenster verwendet", + "Description[en_GB]": "A window switcher layout using large icons to represent the window", + "Description[eo]": "Fenestra ŝaltilo-aranĝo uzante grandajn piktogramojn por reprezenti la fenestron", + "Description[es]": "Un esquema de selección de ventanas que usa iconos grandes para representar las ventanas", + "Description[eu]": "Leihoak aurkezteko ikono handiak erabiltzen dituen leiho-trukatzaile antolamendu bat", + "Description[fi]": "Ikkunavaihtoasettelu suurin ikkunoita esittävin kuvakkein", + "Description[fr]": "Une disposition du sélecteur de fenêtres utilisant de grandes icônes pour représenter les fenêtres", + "Description[gl]": "Unha disposición do selector de xanelas que usa iconas grandes para representar a xanela", + "Description[he]": "פריסת מחליף חלונות שמשתמשת בסמלים גדולים לייצוג החלון", + "Description[hu]": "Ablakváltó elrendezés, amely nagyméretű ikonokat használ az ablakok megjelenítésére", + "Description[ia]": "Un disposition de commutator de fenestra usante icones grande pro representar le fenestra", + "Description[id]": "Sebuah tataletak pengalih jendela menggunakan ikon besar untuk mewakili jendela", + "Description[is]": "Gluggaskiptir sem notar stór tákn til að tákna gluggana", + "Description[it]": "Un selettore delle finestre che utilizza icone grandi per rappresentare la finestra", + "Description[ja]": "ウィンドウを大きいアイコンで表すウィンドウスイッチャー", + "Description[ka]": "ფანჯრების გადართვა დიდი ფანჯრის მაგიერ დიდი ხატულების საშუალებით", + "Description[ko]": "큰 아이콘으로 창을 나타내는 창 전환기 레이아웃", + "Description[lt]": "Langų perjungiklio išdėstymas, kuris langų atvaizdavimui naudoja dideles piktogramas", + "Description[lv]": "Logu pārslēdzēja izkārtojums ar lielām logus reprezentējošām ikonām", + "Description[nl]": "Een indeling van de vensterwisselaar die grote pictogrammen gebruikt om vensters te representeren", + "Description[nn]": "Vindaugsbytar med store ikon for å representera vindauge", + "Description[pl]": "Przełącznik okien z dużymi ikonami", + "Description[pt]": "Uma disposição de mudança de janelas que usa ícones grandes para representar a janela", + "Description[pt_BR]": "Um layout do seletor de janelas que usa ícones grandes para representá-las", + "Description[ro]": "Aranjament pentru comutatorul de ferestre folosind pictograme mari pentru a reprezenta fereastra", + "Description[ru]": "Переключатель окон, показывающий большие значки окон", + "Description[sk]": "Rozloženie prepínača okien používajúce veľké ikony na reprezentáciu okna", + "Description[sl]": "Razporeditev preklopa oken, kjer velike ikone predstavljajo okno", + "Description[sv]": "En layout för fönsterbyte som använder stora ikoner för att representera fönstret", + "Description[tr]": "Pencereyi göstermek için büyük simgeler kullanan bir pencere değiştirici yerleşimi", + "Description[uk]": "Компонування засобу перемикання вікон з великими піктограмами вікон", + "Description[vi]": "Bố cục trình chuyển cửa sổ dùng các biểu tượng cỡ lớn để biểu diễn cửa sổ", + "Description[x-test]": "xxA window switcher layout using large icons to represent the windowxx", + "Description[zh_CN]": "使用大图标代表窗口的窗口切换器布局", + "Description[zh_TW]": "用大圖示代表視窗的視窗切換器佈局", + "Icon": "preferences-system-windows-switcher-big-icons", + "Id": "big_icons", + "License": "GPL", + "Name": "Large Icons", + "Name[ar]": "أيقونات كبيرة", + "Name[az]": "Böyük nişanlar", + "Name[bg]": "Големи икони", + "Name[ca@valencia]": "Icones grans", + "Name[ca]": "Icones grans", + "Name[cs]": "Velké ikony", + "Name[da]": "Store ikoner", + "Name[de]": "Große Symbole", + "Name[en_GB]": "Large Icons", + "Name[eo]": "Grandaj piktogramoj", + "Name[es]": "Iconos grandes", + "Name[eu]": "Ikono handiak", + "Name[fi]": "Suuret kuvakkeet", + "Name[fr]": "Grandes icônes", + "Name[gl]": "Iconas grandes", + "Name[he]": "סמלים גדולים", + "Name[hu]": "Nagy ikonok", + "Name[ia]": "Icones grande", + "Name[id]": "Ikon Besar", + "Name[is]": "Stór tákn", + "Name[it]": "Icone grandi", + "Name[ja]": "大きいアイコン", + "Name[ka]": "დიდი ხატულები", + "Name[ko]": "큰 아이콘", + "Name[lt]": "Didelės piktogramos", + "Name[lv]": "Lielās ikonas", + "Name[nl]": "Grote pictogrammen", + "Name[nn]": "Store ikon", + "Name[pl]": "Duże ikony", + "Name[pt]": "Ícones Grandes", + "Name[pt_BR]": "Ícones grandes", + "Name[ro]": "Pictograme mari", + "Name[ru]": "Крупные значки", + "Name[sk]": "Veľké ikony", + "Name[sl]": "Velike ikone", + "Name[sv]": "Stora ikoner", + "Name[tr]": "Büyük simgeler", + "Name[uk]": "Великі піктограми", + "Name[vi]": "Biểu tượng lớn", + "Name[x-test]": "xxLarge Iconsxx", + "Name[zh_CN]": "大图标", + "Name[zh_TW]": "大圖示" + } +} diff --git a/kwin/windowswitchers/compact/contents/ui/main.qml b/kwin/windowswitchers/compact/contents/ui/main.qml new file mode 100644 index 00000000..38fb97f1 --- /dev/null +++ b/kwin/windowswitchers/compact/contents/ui/main.qml @@ -0,0 +1,190 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2011 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later + */ +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import org.kde.plasma.core as PlasmaCore +import org.kde.kirigami 2.20 as Kirigami +import org.kde.ksvg 1.0 as KSvg +import org.kde.plasma.components 3.0 as PlasmaComponents3 +import org.kde.kwin 3.0 as KWin + +KWin.TabBoxSwitcher { + id: tabBox + currentIndex: compactListView.currentIndex + + /** + * Returns the caption with adjustments for minimized items. + * @param caption the original caption + * @param mimized whether the item is minimized + * @return Caption adjusted for minimized state + **/ + function itemCaption(caption, minimized) { + if (minimized) { + return "(" + caption + ")"; + } + return caption; + } + + TextMetrics { + id: textMetrics + property string longestCaption: tabBox.model.longestCaption() || placeholderLabel.text + text: itemCaption(longestCaption, true) + font.bold: true + } + + onVisibleChanged: { + if (visible) { + // Window captions may have change completely + textMetrics.longestCaption = tabBox.model.longestCaption() || placeholderLabel.text; + } + } + onModelChanged: { + textMetrics.longestCaption = tabBox.model.longestCaption() || placeholderLabel.text; + } + + // For mouse clicking, we delay the activation just enough to have + // visual feedback of the highlight switching to the selected item + Timer { + id: activationTimer + interval: Kirigami.Units.shortDuration + onTriggered: { + tabBox.model.activate(compactListView.currentIndex) + } + } + + PlasmaCore.Dialog { + id: dialog + location: PlasmaCore.Types.Floating + visible: tabBox.visible + flags: Qt.X11BypassWindowManagerHint + x: tabBox.screenGeometry.x + tabBox.screenGeometry.width * 0.5 - dialogMainItem.width * 0.5 + y: tabBox.screenGeometry.y + tabBox.screenGeometry.height * 0.5 - dialogMainItem.height * 0.5 + + mainItem: Item { + id: dialogMainItem + + property int optimalWidth: textMetrics.width + Kirigami.Units.iconSizes.small + 2 * Kirigami.Units.smallSpacing + hoverItem.margins.right + hoverItem.margins.left + property int optimalHeight: compactListView.rowHeight * (compactListView.count || 1) + width: Math.min(Math.max(tabBox.screenGeometry.width * 0.2, optimalWidth), tabBox.screenGeometry.width * 0.8) + height: Math.min(optimalHeight, tabBox.screenGeometry.height * 0.8) + focus: true + + // just to get the margin sizes + KSvg.FrameSvgItem { + id: hoverItem + imagePath: "widgets/viewitem" + prefix: "hover" + visible: false + } + + ListView { + id: compactListView + + // the maximum text width + icon item width (32 + 4 margin) + margins for hover item + property int rowHeight: Math.max(Kirigami.Units.iconSizes.small, textMetrics.height + hoverItem.margins.top + hoverItem.margins.bottom) + + anchors.fill: parent + clip: true + + model: tabBox.model + delegate: RowLayout { + + width: compactListView.width + height: compactListView.rowHeight + opacity: minimized ? 0.6 : 1.0 + + spacing: 2 * Kirigami.Units.smallSpacing + + Kirigami.Icon { + id: iconItem + source: model.icon + Layout.preferredWidth: Kirigami.Units.iconSizes.small + Layout.preferredHeight: Kirigami.Units.iconSizes.small + Layout.leftMargin: hoverItem.margins.left + } + PlasmaComponents3.Label { + id: captionItem + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignBottom + text: itemCaption(caption, minimized) + textFormat: Text.PlainText + font.weight: index === compactListView.currentIndex ? Font.Bold : Font.Normal + elide: Text.ElideMiddle + Layout.fillWidth: true + Layout.rightMargin: hoverItem.margins.right + Layout.topMargin: hoverItem.margins.top + Layout.bottomMargin: hoverItem.margins.bottom + } + TapHandler { + onTapped: { + compactListView.currentIndex = index; + activationTimer.start(); + } + } + } + highlight: KSvg.FrameSvgItem { + imagePath: "widgets/viewitem" + prefix: "hover" + width: compactListView.width + } + highlightMoveDuration: 0 + highlightResizeDuration: 0 + boundsBehavior: Flickable.StopAtBounds + Connections { + target: tabBox + function onCurrentIndexChanged() {compactListView.currentIndex = tabBox.currentIndex;} + } + + RowLayout { + visible: compactListView.count === 0 + anchors.centerIn: parent + spacing: 2 * Kirigami.Units.smallSpacing + + Kirigami.Icon { + source: "edit-none" + Layout.preferredWidth: Kirigami.Units.iconSizes.small + Layout.preferredHeight: Kirigami.Units.iconSizes.small + Layout.leftMargin: hoverItem.margins.left + } + PlasmaComponents3.Label { + id: placeholderLabel + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignBottom + text: i18ndc("kwin", "@info:placeholder no entries in the task switcher", "No open windows") + textFormat: Text.PlainText + Layout.fillWidth: true + Layout.rightMargin: hoverItem.margins.right + Layout.topMargin: hoverItem.margins.top + Layout.bottomMargin: hoverItem.margins.bottom + } + } + } + /* + * Key navigation on outer item for two reasons: + * @li we have to emit the change signal + * @li on multiple invocation it does not work on the list view. Focus seems to be lost. + **/ + Keys.onPressed: { + if (event.key == Qt.Key_Up || event.key == Qt.Key_Left) { + if (compactListView.currentIndex == 0){ + compactListView.currentIndex = compactListView.count-1 + } else { + compactListView.decrementCurrentIndex() + } + } else if (event.key == Qt.Key_Down || event.key == Qt.Key_Right) { + if (compactListView.currentIndex == compactListView.count-1){ + compactListView.currentIndex = 0 + } else { + compactListView.incrementCurrentIndex() + } + } + } + } + } +} diff --git a/kwin/windowswitchers/compact/metadata.json b/kwin/windowswitchers/compact/metadata.json new file mode 100644 index 00000000..3f980f15 --- /dev/null +++ b/kwin/windowswitchers/compact/metadata.json @@ -0,0 +1,142 @@ +{ + "KPackageStructure": "KWin/WindowSwitcher", + "KPlugin": { + "Authors": [ + { + "Email": "mgraesslin@kde.org", + "Name": "Martin Gräßlin", + "Name[ar]": "مارتن جراجلين", + "Name[az]": "Martin Gräßlin", + "Name[bg]": "Martin Gräßlin", + "Name[ca@valencia]": "Martin Gräßlin", + "Name[ca]": "Martin Gräßlin", + "Name[cs]": "Martin Gräßlin", + "Name[da]": "Martin Gräßlin", + "Name[de]": "Martin Gräßlin", + "Name[en_GB]": "Martin Gräßlin", + "Name[eo]": "Martin Gräßlin", + "Name[es]": "Martin Gräßlin", + "Name[eu]": "Martin Gräßlin", + "Name[fi]": "Martin Gräßlin", + "Name[fr]": "Martin Gräßlin", + "Name[gl]": "Martin Gräßlin", + "Name[he]": "מרטין גרייסלין", + "Name[hu]": "Martin Gräßlin", + "Name[ia]": "Martin Gräßlin", + "Name[id]": "Martin Gräßlin", + "Name[is]": "Martin Gräßlin", + "Name[it]": "Martin Gräßlin", + "Name[ja]": "Martin Gräßlin", + "Name[ka]": "მარტინ გრესსლინი", + "Name[ko]": "Martin Gräßlin", + "Name[lt]": "Martin Gräßlin", + "Name[lv]": "Martin Gräßlin", + "Name[nl]": "Martin Gräßlin", + "Name[nn]": "Martin Gräßlin", + "Name[pl]": "Martin Gräßlin", + "Name[pt]": "Martin Gräßlin", + "Name[pt_BR]": "Martin Gräßlin", + "Name[ro]": "Martin Gräßlin", + "Name[ru]": "Martin Gräßlin", + "Name[sk]": "Martin Gräßlin", + "Name[sl]": "Martin Gräßlin", + "Name[sv]": "Martin Gräßlin", + "Name[tr]": "Martin Gräßlin", + "Name[uk]": "Martin Gräßlin", + "Name[vi]": "Martin Gräßlin", + "Name[x-test]": "xxMartin Gräßlinxx", + "Name[zh_CN]": "Martin Gräßlin", + "Name[zh_TW]": "Martin Gräßlin" + } + ], + "Description": "A compact window switcher layout", + "Description[ar]": "مخطط مبدل نوافذ متراص", + "Description[az]": "Yəğcam pəncərə dəyişdirici maketi", + "Description[bg]": "Компактно оформление на програмата за превключване на прозорци", + "Description[ca@valencia]": "Una disposició compacta del commutador de finestres", + "Description[ca]": "Una disposició compacta del commutador de finestres", + "Description[cs]": "Kompaktní rozvržení přepínače oken", + "Description[da]": "Et kompakt vindueskifterlayout", + "Description[de]": "Ein kompaktes Fensterwechsler-Layout", + "Description[en_GB]": "A compact window switcher layout", + "Description[eo]": "Kompakta fenestra ŝaltilo", + "Description[es]": "Un esquema de selección de ventanas compacto", + "Description[eu]": "Leiho-trukatzaile antolamendu trinko bat", + "Description[fi]": "Tiivis ikkunavaihtoasettelu", + "Description[fr]": "Une disposition synthétique pour le basculement de fenêtres", + "Description[gl]": "Unha disposición compacta do selector de xanelas", + "Description[he]": "פריסת מחליף חלונות מצומצמת", + "Description[hu]": "Kompakt ablakváltó elrendezés", + "Description[ia]": "Un disposition de commutator de fenestra compacte", + "Description[id]": "Sebuah tataletak pengalih jendela yang ringkas", + "Description[is]": "Gluggaskiptir með þéttri framsetningu", + "Description[it]": "Una disposizione compatta del selettore delle finestre", + "Description[ja]": "コンパクトなウィンドウスイッチャー", + "Description[ka]": "ფანჯრების გადართვის კომპაქტური განლაგება", + "Description[ko]": "소형 창 전환기 레이아웃", + "Description[lt]": "Kompaktiškas langų perjungiklio išdėstymas", + "Description[lv]": "Kompakts logu pārslēgšanas izkārtojums", + "Description[nl]": "Een compacte indeling van de vensterwisselaar", + "Description[nn]": "Kompakt vindaugsbytar", + "Description[pl]": "Zwarty przełącznik okien", + "Description[pt]": "Uma disposição de mudança de janelas compacta", + "Description[pt_BR]": "Uma layout compacto do seletor de janelas", + "Description[ro]": "Aranjament compact pentru comutatorul de ferestre", + "Description[ru]": "Компактный переключатель окон", + "Description[sk]": "Kompaktné rozloženie prepínača okien", + "Description[sl]": "Kompaktna razporeditev preklopa oken", + "Description[sv]": "En kompakt layout för fönsterbyte", + "Description[tr]": "Sıkışık bir pencere değiştirici yerleşimi", + "Description[uk]": "Компактне компонування засобу перемикання вікон", + "Description[vi]": "Bố cục gọn cho trình chuyển cửa sổ", + "Description[x-test]": "xxA compact window switcher layoutxx", + "Description[zh_CN]": "紧凑的窗口切换器布局", + "Description[zh_TW]": "簡潔的視窗切換器佈局", + "Icon": "preferences-system-windows-switcher-compact", + "Id": "compact", + "License": "GPL", + "Name": "Compact", + "Name[ar]": "متراصّ", + "Name[az]": "Yığcam", + "Name[bg]": "Компактно", + "Name[ca@valencia]": "Compacte", + "Name[ca]": "Compacte", + "Name[cs]": "Kompaktní", + "Name[da]": "Kompakt", + "Name[de]": "Kompakt", + "Name[en_GB]": "Compact", + "Name[eo]": "Kompakta", + "Name[es]": "Compacto", + "Name[eu]": "Trinkoa", + "Name[fi]": "Tiivis", + "Name[fr]": "Synthétique", + "Name[gl]": "Compacta", + "Name[he]": "מצומצם", + "Name[hu]": "Kompakt", + "Name[ia]": "Compacte", + "Name[id]": "Ringkas", + "Name[is]": "Þétt", + "Name[it]": "Compatto", + "Name[ja]": "コンパクト", + "Name[ka]": "დაპატარავება", + "Name[ko]": "축소됨", + "Name[lt]": "Kompaktiškas", + "Name[lv]": "Kompakts", + "Name[nl]": "Compact", + "Name[nn]": "Kompakt", + "Name[pl]": "Zwarty", + "Name[pt]": "Compacto", + "Name[pt_BR]": "Compacto", + "Name[ro]": "Compact", + "Name[ru]": "Компактный", + "Name[sk]": "Kompaktný", + "Name[sl]": "Kompaktno", + "Name[sv]": "Kompakt", + "Name[tr]": "Kompakt", + "Name[uk]": "Компактне", + "Name[vi]": "Gọn", + "Name[x-test]": "xxCompactxx", + "Name[zh_CN]": "紧凑", + "Name[zh_TW]": "簡潔" + } +} diff --git a/kwin/windowswitchers/coverswitch/contents/ui/main.qml b/kwin/windowswitchers/coverswitch/contents/ui/main.qml new file mode 100644 index 00000000..3e45ae79 --- /dev/null +++ b/kwin/windowswitchers/coverswitch/contents/ui/main.qml @@ -0,0 +1,305 @@ +/* + SPDX-FileCopyrightText: 2021 Ismael Asensio + + SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.15 +import QtQuick.Controls 2.15 as QQC2 +import Qt5Compat.GraphicalEffects +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 + +import org.kde.kirigami 2.20 as Kirigami +import org.kde.ksvg 1.0 as KSvg +import org.kde.plasma.components 3.0 as PC3 + +import org.kde.kwin 3.0 as KWin +import org.kde.kwin.private.effects 1.0 + + +KWin.TabBoxSwitcher { + id: tabBox + currentIndex: thumbnailView ? thumbnailView.currentIndex : -1 + + // TODO: Make it user configurable ? + property bool enableBlur: true + + Window { + id: window + + x: tabBox.screenGeometry.x + y: tabBox.screenGeometry.y + width: tabBox.screenGeometry.width + height: tabBox.screenGeometry.height + flags: Qt.BypassWindowManagerHint | Qt.FramelessWindowHint + visibility: Window.FullScreen + // Workaround QTBUG-35244. Do not directly assign here to avoid warning + visible: true + + color: "transparent" + + KWin.DesktopBackground { + activity: KWin.Workspace.currentActivity + desktop: KWin.Workspace.currentVirtualDesktop + outputName: window.screen.name + + layer.enabled: true + layer.effect: FastBlur { + radius: enableBlur ? 64 : 0 + } + } + + Rectangle { + anchors { + top: enableBlur ? parent.top : infoBar.top + topMargin: enableBlur ? 0 : -infoBar.anchors.bottomMargin + left: parent.left + right: parent.right + bottom: parent.bottom + } + color: Kirigami.Theme.backgroundColor + opacity: enableBlur ? 0.5 : 0.75 + } + + Kirigami.PlaceholderMessage { + anchors.centerIn: parent + width: parent.width - Kirigami.Units.largeSpacing * 2 + icon.source: "edit-none" + text: i18ndc("kwin", "@info:placeholder no entries in the task switcher", "No open windows") + visible: thumbnailView.count === 0 + } + + + PathView { + id: thumbnailView + + readonly property int visibleCount: Math.min(count, pathItemCount) + readonly property real boxScaleFactor: 0.5 + readonly property int boxWidth: tabBox.screenGeometry.width * boxScaleFactor + readonly property int boxHeight: tabBox.screenGeometry.height * boxScaleFactor + + focus: true + + anchors.fill: parent + + preferredHighlightBegin: 0.49 + preferredHighlightEnd: preferredHighlightBegin + highlightRangeMode: PathView.StrictlyEnforceRange + + // This property sets the animation duration between the current position to the next one, + // without taking into account how much distance the thumbnails travel in that time. + // To compensate the speed, we slowly reduce the duration with the number of thumbnails, + // starting from `veryLongDuration` when there are 2 of them + highlightMoveDuration: Kirigami.Units.veryLongDuration * (2 / Math.sqrt(visibleCount + 1)) + + pathItemCount: 13 + + path: Path { + // Left stack + startX: thumbnailView.width * 0.1; startY: thumbnailView.height * 0.5 + PathAttribute { name: "progress"; value: 0 } + PathAttribute { name: "scale"; value: 0.7 } + PathAttribute { name: "rotation"; value: 70 } + PathPercent { value: 0 } + + PathLine { x: thumbnailView.width * 0.25 ; y: thumbnailView.height * 0.5 } + PathAttribute { name: "progress"; value: 0.8 } + PathAttribute { name: "scale"; value: 0.7 } + PathAttribute { name: "rotation"; value: 70 } + PathPercent { value: 0.4 } + + // Center Item + PathQuad { + x: thumbnailView.width * 0.5 ; y: thumbnailView.height * 0.6 + controlX: thumbnailView.width * 0.45; controlY: thumbnailView.height * 0.55 + } + PathAttribute { name: "progress"; value: 1 } + PathAttribute { name: "scale"; value: 1 } + PathAttribute { name: "rotation"; value: 0 } + PathPercent { value: 0.49 } // A bit less than 50% so items preferrably stack on the right side + + // Right stack + PathQuad { + x: thumbnailView.width * 0.75 ; y: thumbnailView.height * 0.5 + controlX: thumbnailView.width * 0.55; controlY: thumbnailView.height * 0.55 + } + PathAttribute { name: "progress"; value: 0.8 } + PathAttribute { name: "scale"; value: 0.7 } + PathAttribute { name: "rotation"; value: -70 } + PathPercent { value: 0.6 } + + PathLine { x: thumbnailView.width * 0.9 ; y: thumbnailView.height * 0.5 } + PathAttribute { name: "progress"; value: 0 } + PathAttribute { name: "scale"; value: 0.7 } + PathAttribute { name: "rotation"; value: -70 } + PathPercent { value: 1 } + } + + model: tabBox.model + + delegate: Item { + id: delegateItem + + readonly property string caption: model.caption + readonly property var icon: model.icon + + readonly property real scaleFactor: { + if (thumbnail.implicitWidth < thumbnailView.boxWidth && thumbnail.implicitHeight < thumbnailView.boxHeight) { + // Do not scale up thumbnails smaller than the box frame + return 1; + } else if (thumbnail.ratio > thumbnailView.boxWidth / thumbnailView.boxHeight) { + // Thumbnail is wider than the box + return thumbnailView.boxWidth / thumbnail.implicitWidth; + } else { + // Thumbnail is taller than the box + return thumbnailView.boxHeight / thumbnail.implicitHeight; + } + } + + width: Math.round(thumbnail.implicitWidth * scaleFactor) + height: Math.round(thumbnail.implicitHeight * scaleFactor) + scale: PathView.onPath ? PathView.scale : 0 + z: PathView.onPath ? Math.floor(PathView.progress * thumbnailView.visibleCount) : -1 + + KWin.WindowThumbnail { + id: thumbnail + readonly property double ratio: implicitWidth / implicitHeight + + wId: windowId + anchors.fill: parent + } + + Kirigami.ShadowedRectangle { + anchors.fill: parent + z: -1 + + color: "transparent" + shadow.size: Kirigami.Units.gridUnit + shadow.color: "black" + opacity: 0.5 + } + + transform: Rotation { + origin { x: delegateItem.width/2; y: delegateItem.height/2 } + axis { x: 0; y: 1; z: 0 } + angle: delegateItem.PathView.rotation + } + + TapHandler { + grabPermissions: PointerHandler.TakeOverForbidden + gesturePolicy: TapHandler.WithinBounds + onSingleTapped: { + if (index === thumbnailView.currentIndex) { + thumbnailView.model.activate(index); + return; + } + thumbnailView.movementDirection = (delegateItem.PathView.rotation < 0) ? PathView.Positive : PathView.Negative + thumbnailView.currentIndex = index + } + } + } + + highlight: KSvg.FrameSvgItem { + id: highlightItem + imagePath: "widgets/viewitem" + prefix: "hover" + + readonly property Item target: thumbnailView.currentItem + + visible: target !== null + // Make sure the highlight is pixel perfect aligned on both sides even if the target is not + anchors.centerIn: target + anchors.horizontalCenterOffset: target ? Math.round(target.x) - target.x : 0 + anchors.verticalCenterOffset: target ? Math.round(target.y) - target.y : 0 + width: target ? Math.round(target.width/2 + 3 * Kirigami.Units.smallSpacing) * 2 : 0 + height: target ? Math.round(target.height/2 + 3 * Kirigami.Units.smallSpacing) * 2 : 0 + scale: target ? target.scale : 1 + z: target ? target.z - 0.5 : -0.5 + // The transform cannot be directly assigned as the transform origin is different + transform: Rotation { + origin { x: highlightItem.width/2; y: highlightItem.height/2 } + axis { x: 0; y: 1; z: 0 } + angle: target ? target.PathView.rotation : 0 + } + } + + layer.enabled: true + layer.smooth: true + + onMovementStarted: movementDirection = PathView.Shortest + + Keys.onUpPressed: decrementCurrentIndex() + Keys.onLeftPressed: decrementCurrentIndex() + Keys.onDownPressed: incrementCurrentIndex() + Keys.onRightPressed: incrementCurrentIndex() + } + + RowLayout { + id: infoBar + + height: Kirigami.Units.iconSizes.large + spacing: Kirigami.Units.gridUnit + visible: thumbnailView.count > 0 + + anchors { + horizontalCenter: parent.horizontalCenter + bottom: parent.bottom + margins: Kirigami.Units.gridUnit + } + + Kirigami.Icon { + source: thumbnailView.currentItem?.icon ?? "" + implicitWidth: Kirigami.Units.iconSizes.large + implicitHeight: Kirigami.Units.iconSizes.large + Layout.alignment: Qt.AlignCenter + } + + PC3.Label { + font.bold: true + font.pointSize: Math.round(Kirigami.Theme.defaultFont.pointSize * 1.6) + text: thumbnailView.currentItem?.caption ?? "" + textFormat: Text.PlainText + maximumLineCount: 1 + elide: Text.ElideMiddle + Layout.maximumWidth: tabBox.screenGeometry.width * 0.8 + Layout.alignment: Qt.AlignCenter + } + } + + Kirigami.PlaceholderMessage { + anchors.centerIn: parent + width: parent.width - Kirigami.Units.largeSpacing * 2 + icon.source: "edit-none" + text: i18ndc("kwin", "@info:placeholder no entries in the task switcher", "No open windows") + visible: thumbnailView.count === 0 + } + } + + onCurrentIndexChanged: { + if (currentIndex === thumbnailView.currentIndex) { + return + } + + // WindowSwitcher always changes currentIndex in increments of 1. + // Detect the change direction and set the PathView movement accordingly, so fast changes + // in the same direction don't result into a combination of forward and backward movements. + if (thumbnailView.count === 2 || (currentIndex === 0 && thumbnailView.currentIndex === thumbnailView.count - 1)) { + thumbnailView.movementDirection = PathView.Positive + } else if (currentIndex === thumbnailView.count - 1 && thumbnailView.currentIndex === 0) { + thumbnailView.movementDirection = PathView.Negative + } else { + thumbnailView.movementDirection = (currentIndex > thumbnailView.currentIndex) ? PathView.Positive : PathView.Negative + } + + thumbnailView.currentIndex = tabBox.currentIndex + } + + onVisibleChanged: { + // Reset the PathView index when hiding to avoid unwanted animations on relaunch + if (!visible) { + thumbnailView.currentIndex = 0; + } + window.visible = visible; + } +} diff --git a/kwin/windowswitchers/coverswitch/metadata.json b/kwin/windowswitchers/coverswitch/metadata.json new file mode 100644 index 00000000..3ff55f4f --- /dev/null +++ b/kwin/windowswitchers/coverswitch/metadata.json @@ -0,0 +1,142 @@ +{ + "KPackageStructure": "KWin/WindowSwitcher", + "KPlugin": { + "Authors": [ + { + "Email": "isma.af@gmail.com", + "Name": "Ismael Asensio", + "Name[ar]": "اسماعيل اسينسيو", + "Name[az]": "Ismael Asensio", + "Name[bg]": "Ismael Asensio", + "Name[ca@valencia]": "Ismael Asensio", + "Name[ca]": "Ismael Asensio", + "Name[cs]": "Ismael Asensio", + "Name[da]": "Ismael Asensio", + "Name[de]": "Ismael Asensio", + "Name[en_GB]": "Ismael Asensio", + "Name[eo]": "Ismael Asensio", + "Name[es]": "Ismael Asensio", + "Name[eu]": "Ismael Asensio", + "Name[fi]": "Ismael Asensio", + "Name[fr]": "Ismael Asensio", + "Name[gl]": "Ismael Asensio", + "Name[he]": "איסמעאל אסנסיו", + "Name[hu]": "Ismael Asensio", + "Name[ia]": "Ismael Asensio", + "Name[id]": "Ismael Asensio", + "Name[is]": "Ismael Asensio", + "Name[it]": "Ismael Asensio", + "Name[ja]": "Ismael Asensio", + "Name[ka]": "Ismael Asensio", + "Name[ko]": "Ismael Asensio", + "Name[lt]": "Ismael Asensio", + "Name[lv]": "Ismael Asensio", + "Name[nl]": "Ismael Asensio", + "Name[nn]": "Ismael Asensio", + "Name[pl]": "Ismael Asensio", + "Name[pt]": "Ismael Asensio", + "Name[pt_BR]": "Ismael Asensio", + "Name[ro]": "Ismael Asensio", + "Name[ru]": "Ismael Asensio", + "Name[sk]": "Ismael Asensio", + "Name[sl]": "Ismael Asensio", + "Name[sv]": "Ismael Asensio", + "Name[tr]": "Ismael Asensio", + "Name[uk]": "Ismael Asensio", + "Name[vi]": "Ismael Asensio", + "Name[x-test]": "xxIsmael Asensioxx", + "Name[zh_CN]": "Ismael Asensio", + "Name[zh_TW]": "Ismael Asensio" + } + ], + "Description": "A Window Switcher layout showing thumbnails as a 3D carousel", + "Description[ar]": "مبدل نوافذ يعرض كل النوافذ كمصغرات على شكل دائري ثلاثي الأبعاد.", + "Description[az]": "Miniatürləri 3D karusel şəklində göstərən pəncərə dəyişdirici maketi", + "Description[bg]": "Оформление на превключвател на прозорци, показващо всички прозорци като миниатюри в триизмерна въртележка", + "Description[ca@valencia]": "Una disposició del commutador de finestres que mostra les miniatures com un carrusel en 3D", + "Description[ca]": "Una disposició del commutador de finestres que mostra les miniatures com un carrusel en 3D", + "Description[cs]": "Rozvržení přepínače oken zobrazující náhledy jako 3D kolotoč", + "Description[da]": "Et vindueskifterlayout, der viser thumbnails som en 3D karrusel", + "Description[de]": "Ein Fensterwechsler-Layout, das Vorschauen aller Fenster als 3D-Karussell anzeigt", + "Description[en_GB]": "A Window Switcher layout showing thumbnails as a 3D carousel", + "Description[eo]": "Enpaĝigo de Window Switcher montranta bildetojn kiel 3D karuselo", + "Description[es]": "Un esquema de selección de ventanas que muestra miniaturas como una presentación en 3D", + "Description[eu]": "Koadro-txikiak 3Dko karrusel moduan erakusten dituen leiho-trukatzaile antolamendu bat.", + "Description[fi]": "Ikkunavaihtoasettelu, joka esittää pienoiskuvat kolmiulotteisena karusellina", + "Description[fr]": "Une disposition du sélecteur de fenêtres affichant des vignettes comme un carrousel en 3D", + "Description[gl]": "Unha disposición do selector de xanelas que mostra miniaturas nun carrusel 3D", + "Description[he]": "פריסת מחליף חלונות שמציגה תמונות ממוזערות כסחרחרת תלת־ממדית", + "Description[hu]": "Ablakváltó elrendezés, amely 3D-s körhintaként jeleníti meg a bélyegképeket", + "Description[ia]": "Un disposition de commutator de fenestra monstrante miniaturas como un carosello 3D", + "Description[id]": "Sebuah tataletak pengalih jendela yang menampilkan gambar mini sebagai korsel 3D", + "Description[is]": "Gluggaskiptir sem sýnir smámyndir í þrívíðri hringekju", + "Description[it]": "Una disposizione del selettore delle finestre che mostra le miniature come una giostra 3D", + "Description[ja]": "サムネイルを 3D カルーセルで表示するウィンドウスイッチャー", + "Description[ka]": "ფანჯრების გადართვა მინიატურების 3D კარუსელის ჩვენებით", + "Description[ko]": "섬네일을 3D 상자로 표시하는 창 전환기 레이아웃", + "Description[lt]": "Langų perjungiklio išdėstymas, atvaizduojantis miniatiūras kaip trimatę karuselę", + "Description[lv]": "Logu pārslēgšanas izkārtojums ar sīktēliem kā 3D karuselis", + "Description[nl]": "Een indeling van de vensterwisselaar die miniaturen toont als 3D-carousel", + "Description[nn]": "Vindaugsbytar som viser miniatyrbilete i ein 3D-karusell", + "Description[pl]": "Przełącznik okien z miniaturami jako karuzela 3D", + "Description[pt]": "Uma disposição de mudança de janelas que mostra miniaturas como um carrossel em 3D", + "Description[pt_BR]": "Um layout de seletor de janelas que mostra todas as miniaturas como um carrossel 3D", + "Description[ro]": "Aranjament pentru comutatorul de ferestre arătând miniaturi ca un carusel 3D", + "Description[ru]": "Переключатель окон, использующий объёмный эффект карусели", + "Description[sk]": "Rozloženie prepínača okien zobrazujúce miniatúry ako 3D kolotoč", + "Description[sl]": "Razporeditev preklopa oken, ki prikazuje predogledne sličice kot vrtiljak", + "Description[sv]": "En layout för fönsterbyte som visar miniatyrbilder som en tredimensionell karusell", + "Description[tr]": "Küçük görselleri 3B atlıkarınca olarak gösteren bir pencere değiştirici yerleşimi", + "Description[uk]": "Компонування засобу перемикання вікон з мініатюрами у вигляді просторово каруселі", + "Description[vi]": "Bố cục trình chuyển cửa sổ hiển thị các hình nhỏ thành một băng chuyền 3D", + "Description[x-test]": "xxA Window Switcher layout showing thumbnails as a 3D carouselxx", + "Description[zh_CN]": "使用 3D 转盘风格显示缩略图的窗口切换器", + "Description[zh_TW]": "用 3D 幻燈片顯示預覽的視窗切換器佈局", + "Icon": "preferences-system-windows-effect-coverswitch", + "Id": "coverswitch", + "License": "GPL", + "Name": "Cover Switch", + "Name[ar]": "مبدل المستخدم", + "Name[az]": "Üz qabığı dəyişdiricisi", + "Name[bg]": "Параван", + "Name[ca@valencia]": "Canvi de capa", + "Name[ca]": "Canvi de capa", + "Name[cs]": "Přehlídka oken", + "Name[da]": "Dækselkontakt", + "Name[de]": "3D-Fenstergalerie", + "Name[en_GB]": "Cover Switch", + "Name[eo]": "Kovrilo Ŝaltilo", + "Name[es]": "Selector de portadas", + "Name[eu]": "Leiho-argazkien aldaketa", + "Name[fi]": "Kansikuvavaihto", + "Name[fr]": "Commutateur de couvertures", + "Name[gl]": "Cambio en capas", + "Name[he]": "בורר תמונת כותרת", + "Name[hu]": "Borító", + "Name[ia]": "Commutator de copertura", + "Name[id]": "Cover Switch", + "Name[is]": "Spjaldaskiptir", + "Name[it]": "Selezione circolare", + "Name[ja]": "カバースイッチ", + "Name[ka]": "ყდის შესვლა", + "Name[ko]": "커버 전환기", + "Name[lt]": "Uždengiantis perjungiklis", + "Name[lv]": "Vāka pārslēdzējs", + "Name[nl]": "Omslagtonen", + "Name[nn]": "Omslagvekslar", + "Name[pl]": "Przełączanie okładek", + "Name[pt]": "Mudança de Capas", + "Name[pt_BR]": "Seletor de capa", + "Name[ro]": "Schimbare copertă", + "Name[ru]": "Карусель", + "Name[sk]": "Prepínač krytu", + "Name[sl]": "Stikalo pokrova", + "Name[sv]": "Omslagsbyte", + "Name[tr]": "Kayar görüntüler", + "Name[uk]": "Перемикач обкладинок", + "Name[vi]": "Chuyển kiểu trang bìa", + "Name[x-test]": "xxCover Switchxx", + "Name[zh_CN]": "3D 封面切换", + "Name[zh_TW]": "封面切換" + } +} diff --git a/kwin/windowswitchers/flipswitch/contents/ui/main.qml b/kwin/windowswitchers/flipswitch/contents/ui/main.qml new file mode 100644 index 00000000..364feb73 --- /dev/null +++ b/kwin/windowswitchers/flipswitch/contents/ui/main.qml @@ -0,0 +1,265 @@ +/* + SPDX-FileCopyrightText: 2021 Ismael Asensio + + SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.15 +import QtQuick.Controls 2.15 as QQC2 +import Qt5Compat.GraphicalEffects +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 + +import org.kde.kirigami 2.20 as Kirigami +import org.kde.ksvg 1.0 as KSvg +import org.kde.plasma.components 3.0 as PC3 + +import org.kde.kwin 3.0 as KWin +import org.kde.kwin.private.effects 1.0 + + +KWin.TabBoxSwitcher { + id: tabBox + currentIndex: thumbnailView ? thumbnailView.currentIndex : -1 + + // TODO: Make it user configurable ? + property bool enableBlur: true + + Window { + id: window + + x: tabBox.screenGeometry.x + y: tabBox.screenGeometry.y + width: tabBox.screenGeometry.width + height: tabBox.screenGeometry.height + flags: Qt.BypassWindowManagerHint | Qt.FramelessWindowHint + visibility: Window.FullScreen + // Workaround QTBUG-35244. Do not directly assign here to avoid warning + visible: true + + color: "transparent" + + KWin.DesktopBackground { + activity: KWin.Workspace.currentActivity + desktop: KWin.Workspace.currentVirtualDesktop + outputName: window.screen.name + + layer.enabled: true + layer.effect: FastBlur { + radius: enableBlur ? 64 : 0 + } + } + + Rectangle { + anchors { + top: enableBlur ? parent.top : infoBar.top + topMargin: enableBlur ? 0 : -infoBar.anchors.bottomMargin + left: parent.left + right: parent.right + bottom: parent.bottom + } + color: Kirigami.Theme.backgroundColor + opacity: enableBlur ? 0.5 : 0.75 + } + + PathView { + id: thumbnailView + + readonly property int visibleCount: Math.min(count, pathItemCount) + // Make thumbnails slightly smaller the more there are, so it doesn't feel too crowded + // The sizeFactor curve parameters have been calculated experimentally + readonly property real boxScaleFactor: 0.35 + (0.5 / (visibleCount + 1)) + readonly property int boxWidth: tabBox.screenGeometry.width * boxScaleFactor + readonly property int boxHeight: tabBox.screenGeometry.height * boxScaleFactor + + focus: true + + anchors.fill: parent + + preferredHighlightBegin: 1/(visibleCount + 1) + preferredHighlightEnd: preferredHighlightBegin + highlightRangeMode: PathView.StrictlyEnforceRange + + // This property sets the animation duration between the current position to the next one, + // without taking into account how much distance the thumbnails travel in that time. + // To compensate the speed, we slowly reduce the duration with the number of thumbnails, + // starting from `veryLongDuration` when there are 2 of them + highlightMoveDuration: Kirigami.Units.veryLongDuration * (2 / Math.sqrt(visibleCount + 1)) + + pathItemCount: 12 + + path: Path { + // Nearest point of the path + startX: Math.round(thumbnailView.width * 0.75) + startY: Math.round(thumbnailView.height * 0.80) + PathAttribute { name: "progress"; value: 1 } + PathAttribute { name: "scale"; value: 1 } + + // Back point of the path on top-left corner + PathLine { + x: Math.round(thumbnailView.width * 0.25) + y: Math.round(thumbnailView.height * 0.20) + } + PathAttribute { name: "progress"; value: 0 } + PathAttribute { name: "scale"; value: 0.6 } + } + + model: tabBox.model + + delegate: Item { + readonly property string caption: model.caption + readonly property var icon: model.icon + + readonly property real scaleFactor: { + if (thumbnail.implicitWidth < thumbnailView.boxWidth && thumbnail.implicitHeight < thumbnailView.boxHeight) { + // Do not scale up thumbnails smaller than the box frame + return 1; + } else if (thumbnail.ratio > thumbnailView.boxWidth / thumbnailView.boxHeight) { + // Thumbnail is wider than the box + return thumbnailView.boxWidth / thumbnail.implicitWidth; + } else { + // Thumbnail is taller than the box + return thumbnailView.boxHeight / thumbnail.implicitHeight; + } + } + + width: Math.round(thumbnail.implicitWidth * scaleFactor) + height: Math.round(thumbnail.implicitHeight * scaleFactor) + scale: PathView.onPath ? PathView.scale : 0 + z: PathView.onPath ? Math.floor(PathView.progress * thumbnailView.visibleCount) : -1 + + // Reduce opacity on the end so items dissapear more naturally + opacity: Math.min(1, (1 - PathView.progress) / thumbnailView.preferredHighlightBegin); + + KWin.WindowThumbnail { + id: thumbnail + readonly property double ratio: implicitWidth / implicitHeight + + wId: windowId + anchors.fill: parent + } + + Kirigami.ShadowedRectangle { + anchors.fill: parent + z: -1 + + color: "transparent" + shadow.size: Kirigami.Units.gridUnit + shadow.color: "black" + opacity: 0.5 + shadow.yOffset: 1 + } + + TapHandler { + grabPermissions: PointerHandler.TakeOverForbidden + gesturePolicy: TapHandler.WithinBounds + onSingleTapped: { + if (index === thumbnailView.currentIndex) { + thumbnailView.model.activate(index); + return; + } + thumbnailView.movementDirection = PathView.Positive + thumbnailView.currentIndex = index + } + } + } + + transform: Rotation { + origin { x: thumbnailView.width/2; y: thumbnailView.height/2 } + axis { x: 0; y: 1; z: -0.15 } + angle: 10 + } + + highlight: KSvg.FrameSvgItem { + imagePath: "widgets/viewitem" + prefix: "hover" + + readonly property Item target: thumbnailView.currentItem + + visible: target !== null + anchors.centerIn: target + width: target ? target.width + 6 * Kirigami.Units.smallSpacing : 0 + height: target ? target.height + 6 * Kirigami.Units.smallSpacing : 0 + scale: target ? target.scale : 1 + z: target ? target.z - 0.5 : -0.5 + } + + layer.enabled: true + layer.smooth: true + + onMovementStarted: movementDirection = PathView.Shortest + + Keys.onUpPressed: decrementCurrentIndex() + Keys.onLeftPressed: decrementCurrentIndex() + Keys.onDownPressed: incrementCurrentIndex() + Keys.onRightPressed: incrementCurrentIndex() + } + + RowLayout { + id: infoBar + + height: Kirigami.Units.iconSizes.large + spacing: Kirigami.Units.gridUnit + visible: thumbnailView.count > 0 + + anchors { + horizontalCenter: parent.horizontalCenter + bottom: parent.bottom + margins: Kirigami.Units.gridUnit + } + + Kirigami.Icon { + source: thumbnailView.currentItem?.icon ?? "" + implicitWidth: Kirigami.Units.iconSizes.large + implicitHeight: Kirigami.Units.iconSizes.large + Layout.alignment: Qt.AlignCenter + } + + PC3.Label { + font.bold: true + font.pointSize: Math.round(Kirigami.Theme.defaultFont.pointSize * 1.6) + text: thumbnailView.currentItem?.caption ?? "" + textFormat: Text.PlainText + maximumLineCount: 1 + elide: Text.ElideMiddle + Layout.maximumWidth: tabBox.screenGeometry.width * 0.8 + Layout.alignment: Qt.AlignCenter + } + } + + Kirigami.PlaceholderMessage { + anchors.centerIn: parent + width: parent.width - Kirigami.Units.largeSpacing * 2 + icon.source: "edit-none" + text: i18ndc("kwin", "@info:placeholder no entries in the task switcher", "No open windows") + visible: thumbnailView.count === 0 + } + } + + onCurrentIndexChanged: { + if (currentIndex === thumbnailView.currentIndex) { + return + } + + // WindowSwitcher always changes currentIndex in increments of 1. + // Detect the change direction and set the PathView movement accordingly, so fast changes + // in the same direction don't result into a combination of forward and backward movements. + if (thumbnailView.count === 2 || (currentIndex === 0 && thumbnailView.currentIndex === thumbnailView.count - 1)) { + thumbnailView.movementDirection = PathView.Positive + } else if (currentIndex === thumbnailView.count - 1 && thumbnailView.currentIndex === 0) { + thumbnailView.movementDirection = PathView.Negative + } else { + thumbnailView.movementDirection = (currentIndex > thumbnailView.currentIndex) ? PathView.Positive : PathView.Negative + } + + thumbnailView.currentIndex = tabBox.currentIndex + } + + onVisibleChanged: { + // Reset the PathView index when hiding to avoid unwanted animations on relaunch + if (!visible) { + thumbnailView.currentIndex = 0; + } + window.visible = visible; + } +} diff --git a/kwin/windowswitchers/flipswitch/metadata.json b/kwin/windowswitchers/flipswitch/metadata.json new file mode 100644 index 00000000..52637c7f --- /dev/null +++ b/kwin/windowswitchers/flipswitch/metadata.json @@ -0,0 +1,142 @@ +{ + "KPackageStructure": "KWin/WindowSwitcher", + "KPlugin": { + "Authors": [ + { + "Email": "isma.af@gmail.com", + "Name": "Ismael Asensio", + "Name[ar]": "اسماعيل اسينسيو", + "Name[az]": "Ismael Asensio", + "Name[bg]": "Ismael Asensio", + "Name[ca@valencia]": "Ismael Asensio", + "Name[ca]": "Ismael Asensio", + "Name[cs]": "Ismael Asensio", + "Name[da]": "Ismael Asensio", + "Name[de]": "Ismael Asensio", + "Name[en_GB]": "Ismael Asensio", + "Name[eo]": "Ismael Asensio", + "Name[es]": "Ismael Asensio", + "Name[eu]": "Ismael Asensio", + "Name[fi]": "Ismael Asensio", + "Name[fr]": "Ismael Asensio", + "Name[gl]": "Ismael Asensio", + "Name[he]": "איסמעאל אסנסיו", + "Name[hu]": "Ismael Asensio", + "Name[ia]": "Ismael Asensio", + "Name[id]": "Ismael Asensio", + "Name[is]": "Ismael Asensio", + "Name[it]": "Ismael Asensio", + "Name[ja]": "Ismael Asensio", + "Name[ka]": "Ismael Asensio", + "Name[ko]": "Ismael Asensio", + "Name[lt]": "Ismael Asensio", + "Name[lv]": "Ismael Asensio", + "Name[nl]": "Ismael Asensio", + "Name[nn]": "Ismael Asensio", + "Name[pl]": "Ismael Asensio", + "Name[pt]": "Ismael Asensio", + "Name[pt_BR]": "Ismael Asensio", + "Name[ro]": "Ismael Asensio", + "Name[ru]": "Ismael Asensio", + "Name[sk]": "Ismael Asensio", + "Name[sl]": "Ismael Asensio", + "Name[sv]": "Ismael Asensio", + "Name[tr]": "Ismael Asensio", + "Name[uk]": "Ismael Asensio", + "Name[vi]": "Ismael Asensio", + "Name[x-test]": "xxIsmael Asensioxx", + "Name[zh_CN]": "Ismael Asensio", + "Name[zh_TW]": "Ismael Asensio" + } + ], + "Description": "A Window Switcher layout showing thumbnails as a 3D card deck", + "Description[ar]": "مبدل نوافذ يعرض كل النوافذ كمجموعة بطاقات ثلاثية الأبعاد", + "Description[az]": "Miniatürləri 3D kard dəsti kimi göstərən pəncərə dəyişdirici maketi", + "Description[bg]": "Оформление на превключвател на прозорци, показващо всички прозорци като миниатюри в триизмерен плот за игра на карти", + "Description[ca@valencia]": "Una disposició del commutador de finestres que mostra les miniatures com una baralla de cartes en 3D", + "Description[ca]": "Una disposició del commutador de finestres que mostra les miniatures com una baralla de cartes en 3D", + "Description[cs]": "Rozvržení přepínače oken zobrazující náhledy jako 3D balíček karet", + "Description[da]": "Et vindueskifterlayout, der viser thumbnails som et 3D kortspil", + "Description[de]": "Ein Fensterwechsler-Layout, das Vorschauen aller Fenster als 3D-Kartenstapel anzeigt", + "Description[en_GB]": "A Window Switcher layout showing thumbnails as a 3D card deck", + "Description[eo]": "Enpaĝigo de Window Switcher montranta bildetojn kiel 3D-ludkartaro", + "Description[es]": "Un esquema de selección de ventanas que muestra miniaturas como una baraja de cartas en 3D", + "Description[eu]": "Koadro-txikiak 3Dko karta-sorta gisa erakusten dituen leiho-trukatzaile antolamendu bat", + "Description[fi]": "Ikkunavaihtoasettelu, joka näyttää pienoiskuvat kolmiulotteisena korttipakkana", + "Description[fr]": "Une disposition du sélecteur de fenêtres affichant des vignettes comme un jeu de cartes en 3D", + "Description[gl]": "Unha disposición do selector de xanelas que mostra miniaturas nunha baralla 3D", + "Description[he]": "פריסת מחליף חלונות שמציגה תמונות ממוזערות כחפיסת קלפים תלת־ממדית", + "Description[hu]": "Ablakváltó elrendezés, amely 3D-s kártyapakliként jeleníti meg a bélyegképeket", + "Description[ia]": "Un disposition de commutator de fenestra monstrante miniaturas como un joco de cartas", + "Description[id]": "Sebuah tataletak pengalih jendela yang menampilkan gambar mini sebagai dek kartu 3D", + "Description[is]": "Gluggaskiptir sem sýnir smámyndir í þrívíðum spilastokki", + "Description[it]": "Una disposizione del selettore delle finestre che mostra le miniature come mazzo di carte 3D", + "Description[ja]": "サムネイルを 3D カードデッキで表示するウィンドウスイッチャー", + "Description[ka]": "ფანჯრების გადართვა 3D ბარათის მაგიდის ჩვენებით", + "Description[ko]": "섬네일을 3D 카드 덱으로 표시하는 창 전환기 레이아웃", + "Description[lt]": "Langų perjungiklio išdėstymas, atvaizduojantis miniatiūras kaip trimatę kortų kaladę", + "Description[lv]": "Logu pārslēdzēja izkārtojums ar sīktēliem kā 3D kāršu kavu", + "Description[nl]": "Een indeling van de vensterwisselaar die miniaturen toont als een 3D-kaartspel", + "Description[nn]": "Vindaugsbytar som viser miniatyrbilete som ein 3D-kortstokk", + "Description[pl]": "Przełącznik okien z miniaturami jako stos kart 3D", + "Description[pt]": "Uma disposição de mudança de janelas que mostra miniaturas como um baralho de cartas em 3D", + "Description[pt_BR]": "Um layout de seletor de janelas que mostra todas as miniaturas como um baralho de cartas 3D", + "Description[ro]": "Aranjament pentru comutatorul de ferestre arătând miniaturi ca pachet de cărți 3D", + "Description[ru]": "Переключатель окон, использующий объёмный эффект стопки карточек", + "Description[sk]": "Rozloženie prepínača okien zobrazujúce miniatúry ako balíček 3D kariet", + "Description[sl]": "Razporeditev preklopa oken, ki prikazuje predogledne sličice kot 3D zbirko kart", + "Description[sv]": "En layout för fönsterbyte som visar miniatyrbilder som en tredimensionell kortlek", + "Description[tr]": "Küçük görselleri 3B kart destesi olarak gösteren bir pencere değiştirici yerleşimi", + "Description[uk]": "Компонування засобу перемикання вікон з мініатюрами вікон у вигляді колоди карт", + "Description[vi]": "Bố cục trình chuyển cửa sổ hiển thị các hình nhỏ thành một tập bài 3D", + "Description[x-test]": "xxA Window Switcher layout showing thumbnails as a 3D card deckxx", + "Description[zh_CN]": "使用 3D 翻牌风格显示缩略图的窗口切换器", + "Description[zh_TW]": "用 3D 牌組顯示預覽的視窗切換器佈局", + "Icon": "preferences-system-windows-effect-flipswitch", + "Id": "flipswitch", + "License": "GPL", + "Name": "Flip Switch", + "Name[ar]": "مبدل التقليب", + "Name[az]": "Kartlar", + "Name[bg]": "Прелистване", + "Name[ca@valencia]": "Canvi en roda", + "Name[ca]": "Canvi en roda", + "Name[cs]": "Kartotéka", + "Name[da]": "Vippekontakt", + "Name[de]": "3D-Fensterstapel", + "Name[en_GB]": "Flip Switch", + "Name[eo]": "Flip Ŝaltilo", + "Name[es]": "Selector de volteo", + "Name[eu]": "Biratze aldaketa", + "Name[fi]": "Kääntövaihto", + "Name[fr]": "Empilement en perspective", + "Name[gl]": "Cambio en fila", + "Name[he]": "בורר החלפה", + "Name[hu]": "Tükrözés", + "Name[ia]": "Commutator de colpetto (Flip)", + "Name[id]": "Flip Switch", + "Name[is]": "Flettiskiptir", + "Name[it]": "Selezione a pila", + "Name[ja]": "フリップスイッチ", + "Name[ka]": "გადართვა", + "Name[ko]": "플립 전환기", + "Name[lt]": "Gulsčiasis perjungiklis", + "Name[lv]": "Apmešanas pārslēdzējs", + "Name[nl]": "Flip Switch", + "Name[nn]": "Stabelvekslar", + "Name[pl]": "Przełączanie przebierane", + "Name[pt]": "Mudança por Inversão", + "Name[pt_BR]": "Interruptor Flip", + "Name[ro]": "Schimbare cu întoarcere", + "Name[ru]": "Перелистывание", + "Name[sk]": "Prepínač", + "Name[sl]": "Stikalo pregiba", + "Name[sv]": "Blädderbyte", + "Name[tr]": "Görüntü destesi", + "Name[uk]": "Тасування карток", + "Name[vi]": "Chuyển kiểu lật", + "Name[x-test]": "xxFlip Switchxx", + "Name[zh_CN]": "3D 翻牌切换", + "Name[zh_TW]": "翻轉切換" + } +} diff --git a/kwin/windowswitchers/sidebar/contents/ui/main.qml b/kwin/windowswitchers/sidebar/contents/ui/main.qml new file mode 100644 index 00000000..442b6141 --- /dev/null +++ b/kwin/windowswitchers/sidebar/contents/ui/main.qml @@ -0,0 +1,130 @@ +/* + SPDX-FileCopyrightText: 2011 Martin Gräßlin + SPDX-FileCopyrightText: 2013 Marco Martin + SPDX-FileCopyrightText: 2016 Kai Uwe Broulik + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +import QtQuick 2.0 +import QtQuick.Layouts 1.1 +import org.kde.plasma.components 3.0 as PlasmaComponents +import org.kde.plasma.core as PlasmaCore +import org.kde.kirigami 2.20 as Kirigami +import org.kde.plasma.extras 2.0 as PlasmaExtras +import org.kde.kwin 3.0 as KWin + +KWin.TabBoxSwitcher { + id: tabBox + + readonly property real screenFactor: screenGeometry.width / screenGeometry.height + + currentIndex: thumbnailListView.currentIndex + + PlasmaCore.Dialog { + id: dialog + location: Qt.application.layoutDirection === Qt.RightToLeft ? PlasmaCore.Types.RightEdge : PlasmaCore.Types.LeftEdge + visible: tabBox.visible + flags: Qt.X11BypassWindowManagerHint + x: screenGeometry.x + (Qt.application.layoutDirection === Qt.RightToLeft ? screenGeometry.width - width : 0) + y: screenGeometry.y + + mainItem: PlasmaComponents.ScrollView { + id: dialogMainItem + + focus: true + + contentWidth: tabBox.screenGeometry.width * 0.15 + height: tabBox.screenGeometry.height - dialog.margins.top - dialog.margins.bottom + + LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft + LayoutMirroring.childrenInherit: true + + ListView { + id: thumbnailListView + focus: true + model: tabBox.model + spacing: Kirigami.Units.smallSpacing + highlightMoveDuration: Kirigami.Units.longDuration + highlightResizeDuration: 0 + + Connections { + target: tabBox + function onCurrentIndexChanged() { + thumbnailListView.currentIndex = tabBox.currentIndex; + thumbnailListView.positionViewAtIndex(thumbnailListView.currentIndex, ListView.Contain) + } + } + + delegate: MouseArea { + width: thumbnailListView.width + height: delegateColumn.height + 2 * delegateColumn.y + focus: ListView.isCurrentItem + + Accessible.name: model.caption + Accessible.role: Accessible.ListItem + + onClicked: { + if (tabBox.noModifierGrab) { + tabBox.model.activate(index); + } else { + thumbnailListView.currentIndex = index; + } + } + + ColumnLayout { + id: delegateColumn + anchors.horizontalCenter: parent.horizontalCenter + // anchors.centerIn causes layouting glitches + y: Kirigami.Units.smallSpacing + width: parent.width - 2 * Kirigami.Units.smallSpacing + spacing: Kirigami.Units.smallSpacing + + Item { + Layout.fillWidth: true + implicitHeight: Math.round(delegateColumn.width / tabBox.screenFactor) + + KWin.WindowThumbnail { + anchors.fill: parent + wId: windowId + } + } + + RowLayout { + spacing: Kirigami.Units.smallSpacing + Layout.fillWidth: true + + Kirigami.Icon { + Layout.preferredHeight: Kirigami.Units.iconSizes.medium + Layout.preferredWidth: Kirigami.Units.iconSizes.medium + source: model.icon + } + + Kirigami.Heading { + Layout.fillWidth: true + height: undefined + level: 4 + text: model.caption + elide: Text.ElideRight + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + maximumLineCount: 2 + lineHeight: 0.95 + textFormat: Text.PlainText + } + } + } + } + + highlight: PlasmaExtras.Highlight {} + + Kirigami.PlaceholderMessage { + anchors.centerIn: parent + width: parent.width - Kirigami.Units.largeSpacing * 2 + icon.source: "edit-none" + text: i18ndc("kwin", "@info:placeholder no entries in the task switcher", "No open windows") + visible: thumbnailListView.count === 0 + } + } + } + } +} + diff --git a/kwin/windowswitchers/sidebar/metadata.json b/kwin/windowswitchers/sidebar/metadata.json new file mode 100644 index 00000000..7bd00550 --- /dev/null +++ b/kwin/windowswitchers/sidebar/metadata.json @@ -0,0 +1,132 @@ +{ + "KPackageStructure": "KWin/WindowSwitcher", + "KPlugin": { + "Authors": [ + { + "Email": "plasma-devel@kde.org", + "Name": "KDE Visual Design Group", + "Name[ar]": "مجموعة التصميم المرئي لكِيدِي", + "Name[bg]": "KDE Visual Design Group", + "Name[ca@valencia]": "Grup de disseny visual de KDE", + "Name[ca]": "Grup de disseny visual de KDE", + "Name[cs]": "Skupina vizuálního návrhu KDE", + "Name[da]": "KDE visuelt design gruppe", + "Name[de]": "KDE Visual Design Group", + "Name[en_GB]": "KDE Visual Design Group", + "Name[eo]": "KDE Vida Dezajn-Grupo", + "Name[es]": "Grupo de diseño visual de KDE", + "Name[eu]": "KDE Ikus-diseinu taldea", + "Name[fi]": "KDE:n visuaalinen suunnitteluryhmä", + "Name[fr]": "Groupe de conception graphique de KDE", + "Name[gl]": "Grupo de deseño visual de KDE", + "Name[he]": "קבוצת העיצוב החזותי של KDE", + "Name[hu]": "KDE Visual Design Group", + "Name[ia]": "KDE Visual Design Group (Gruppo de Designo Visual de KDE)", + "Name[is]": "KDE-hópurinn í myndrænni hönnun", + "Name[it]": "KDE Visual Design Group", + "Name[ja]": "KDE Visual Design Group", + "Name[ka]": "KDE-ის ვიზუალური დიზაინის ჯგუფი", + "Name[ko]": "KDE 시각 디자인 그룹", + "Name[lt]": "KDE vaizdinio dizaino grupė", + "Name[lv]": "KDE Visual Design Group", + "Name[nl]": "KDE Visuele ontwerpgroep", + "Name[nn]": "KDE Visual Design-gruppa", + "Name[pl]": "Grupa oprawy graficznej KDE", + "Name[pt_BR]": "Grupo de design visual do KDE (KDE VDG)", + "Name[ro]": "KDE Visual Design Group", + "Name[ru]": "Группа KDE Visual Design", + "Name[sk]": "KDE Visual Design Group", + "Name[sl]": "Skupina vizualnega designa KDE", + "Name[sv]": "KDE:s visuella designgrupp", + "Name[tr]": "KDE Görsel Tasarım Grubu", + "Name[uk]": "Група з візуального дизайну KDE", + "Name[vi]": "Đội Thiết kế Trực quan KDE", + "Name[x-test]": "xxKDE Visual Design Groupxx", + "Name[zh_CN]": "KDE 视觉设计团队", + "Name[zh_TW]": "KDE Visual Design Group" + } + ], + "Description": "Window switcher layout using a vertical sidebar", + "Description[ar]": "مخطط مبدل نوافذ يستخدم شريط جانبي عمودي", + "Description[bg]": "Оформление на програмата за превключване на прозорци с използване на вертикална странична лента", + "Description[ca@valencia]": "Una disposició del commutador de finestres que utilitza una barra lateral", + "Description[ca]": "Una disposició del commutador de finestres que utilitza una barra lateral", + "Description[cs]": "Přepínač oken s pomocí svislého postranního panelu", + "Description[da]": "Vindueskifterlayout, der bruger et lodret sidepanel", + "Description[de]": "Ein Fensterwechsler-Layout, das eine vertikale Seitenleiste verwendet", + "Description[en_GB]": "Window switcher layout using a vertical sidebar", + "Description[eo]": "Fenestra ŝaltilo-aranĝo uzante vertikalan flankbreton", + "Description[es]": "Esquema de selección de ventanas que usa una barra lateral vertical", + "Description[eu]": "Alboko-barra bertikal bat erabiltzen duen leiho-trukatzaile antolamendu bat", + "Description[fi]": "Pystysivupalkkia käyttävä ikkunavaihto", + "Description[fr]": "Une disposition du sélecteur de fenêtres utilisant un panneau latéral vertical", + "Description[gl]": "Unha disposición do selector de xanelas que usa unha barra lateral vertical", + "Description[he]": "פריסת מחליף חלונות באמצעות סרגל צד אנכי", + "Description[hu]": "Függőleges oldalsávot használó ablakváltó elrendezés", + "Description[ia]": "Un disposition de commutator de fenestra usante un barra lateral vertical", + "Description[is]": "Gluggaskiptir sem notar lóðrétta hliðarstiku", + "Description[it]": "Disposizione del cambio finestre che utilizza una barra laterale verticale", + "Description[ja]": "垂直なサイドバーを使用するウィンドウスイッチャー", + "Description[ka]": "ფანჯრების განლაგების გადართვა ვერტიკალური პანელის საშუალებით", + "Description[ko]": "수직 사이드바를 사용하는 창 전환기 레이아웃", + "Description[lt]": "Langų perjungiklio išdėstymas, naudojantis vertikalią šoninę juostą", + "Description[lv]": "Logu pārslēgšanas izkārtojums ar vertikālu sānu joslu", + "Description[nl]": "Indeling van de vensterwisselaar met verticale zijbalk", + "Description[nn]": "Vindaugsbytar som brukar ein loddrett sidestolpe", + "Description[pl]": "Układ przełącznika okien używający pionowego paska bocznego", + "Description[pt_BR]": "Layout do alternador de janelas usando uma barra lateral vertical", + "Description[ro]": "Aranjament pentru comutatorul de ferestre folosind o bară laterală verticală", + "Description[ru]": "Переключатель окон в виде боковой панели с вертикальным расположением элементов", + "Description[sk]": "Rozloženie prepínača okien používajúce zvislý posuvník", + "Description[sl]": "Okenski preklopnik postavitve, ki uporablja navpični drsnik", + "Description[sv]": "Layout för fönsterbyte som använder en vertikal sidorad", + "Description[tr]": "Dikey bir kenar çubuğu kullanan pencere değiştirici yerleşimi", + "Description[uk]": "Компонування перемикача вікон за допомогою вертикальної бічної панелі", + "Description[vi]": "Bố cục trình chuyển cửa sổ dùng một thanh bên dọc", + "Description[x-test]": "xxWindow switcher layout using a vertical sidebarxx", + "Description[zh_CN]": "使用垂直侧边栏的窗口切换器布局", + "Description[zh_TW]": "使用垂直側邊欄的視窗切換器佈局", + "Id": "sidebar", + "License": "GPLv2+", + "Name": "Sidebar", + "Name[ar]": "شريط جانبي", + "Name[bg]": "Странична лента", + "Name[ca@valencia]": "Barra lateral", + "Name[ca]": "Barra lateral", + "Name[cs]": "Postranní panel", + "Name[da]": "Sidepanel", + "Name[de]": "Seitenleiste", + "Name[en_GB]": "Sidebar", + "Name[eo]": "Flankbreto", + "Name[es]": "Barra lateral", + "Name[eu]": "Alboko barra", + "Name[fi]": "Sivupalkki", + "Name[fr]": "Panneau latéral", + "Name[gl]": "Barra lateral", + "Name[he]": "סרגל צד", + "Name[hu]": "Oldalsáv", + "Name[ia]": "Barra lateral", + "Name[is]": "Hliðarstika", + "Name[it]": "Barra laterale", + "Name[ja]": "サイドバー", + "Name[ka]": "გვერდითა ზოლი", + "Name[ko]": "사이드바", + "Name[lt]": "Šoninė juosta", + "Name[lv]": "Sānu josla", + "Name[nl]": "Zijbalk", + "Name[nn]": "Sidestolpe", + "Name[pl]": "Pasek boczny", + "Name[pt_BR]": "Barra lateral", + "Name[ro]": "Bară laterală", + "Name[ru]": "Боковая панель", + "Name[sk]": "Bočný panel", + "Name[sl]": "Stranski drsnik", + "Name[sv]": "Sidorad", + "Name[tr]": "Kenar çubuğu", + "Name[uk]": "Бічна панель", + "Name[vi]": "Thanh bên", + "Name[x-test]": "xxSidebarxx", + "Name[zh_CN]": "侧边栏", + "Name[zh_TW]": "側邊欄" + } +} diff --git a/plasmacalendarplugins/CMakeLists.txt b/plasmacalendarplugins/CMakeLists.txt new file mode 100644 index 00000000..9f898e9c --- /dev/null +++ b/plasmacalendarplugins/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(astronomical) +add_subdirectory(alternatecalendar) diff --git a/plasmacalendarplugins/alternatecalendar/CMakeLists.txt b/plasmacalendarplugins/alternatecalendar/CMakeLists.txt new file mode 100644 index 00000000..4790e086 --- /dev/null +++ b/plasmacalendarplugins/alternatecalendar/CMakeLists.txt @@ -0,0 +1,40 @@ +# SPDX-FileCopyrightText: 2022 Fushan Wen + +# SPDX-License-Identifier: GPL-2.0-or-later + +set(alternatecalendar_LIB_SRCS + alternatecalendarplugin.cpp + calendarsystem.h + provider/abstractcalendarprovider.cpp + provider/qtcalendar.cpp +) + +set(alternatecalendar_LINK_LIBRARIES + KF6::ConfigCore + KF6::CalendarEvents + KF6::I18n +) + +if(${HAVE_ICU}) + set(alternatecalendar_LIB_SRCS + ${alternatecalendar_LIB_SRCS} + provider/icucalendar_p.cpp + provider/chinesecalendar.cpp + provider/hebrewcalendar.cpp + provider/indiancalendar.cpp + provider/islamiccalendar.cpp + provider/solarutils.h + ) + set(alternatecalendar_LINK_LIBRARIES + ${alternatecalendar_LINK_LIBRARIES} + ICU::uc + ICU::i18n + ) +endif() + +configure_file(config-ICU.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-ICU.h) + +kcoreaddons_add_plugin(alternatecalendar SOURCES ${alternatecalendar_LIB_SRCS} INSTALL_NAMESPACE "plasmacalendarplugins") +target_link_libraries(alternatecalendar ${alternatecalendar_LINK_LIBRARIES}) + +add_subdirectory(config) diff --git a/plasmacalendarplugins/alternatecalendar/Messages.sh b/plasmacalendarplugins/alternatecalendar/Messages.sh new file mode 100644 index 00000000..eeaf4324 --- /dev/null +++ b/plasmacalendarplugins/alternatecalendar/Messages.sh @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: 2022 Fushan Wen + +# SPDX-License-Identifier: GPL-2.0-or-later + +#!/usr/bin/env bash +$EXTRACTRC `find . -name \*.rc -o -name \*.ui -o -name \*.kcfg` >> rc.cpp +$XGETTEXT `find . -name \*.js -o -name \*.qml -o -name \*.cpp -o -name \*.h` -o $podir/plasma_calendar_alternatecalendar.pot +rm -f rc.cpp diff --git a/plasmacalendarplugins/alternatecalendar/alternatecalendarplugin.cpp b/plasmacalendarplugins/alternatecalendar/alternatecalendarplugin.cpp new file mode 100644 index 00000000..c8442412 --- /dev/null +++ b/plasmacalendarplugins/alternatecalendar/alternatecalendarplugin.cpp @@ -0,0 +1,141 @@ +/* + SPDX-FileCopyrightText: 2022 Fushan Wen + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "config-ICU.h" + +#include "alternatecalendarplugin.h" + +#include + +#include + +#include "provider/qtcalendar.h" +#if HAVE_ICU +#include "provider/chinesecalendar.h" +#include "provider/hebrewcalendar.h" +#include "provider/indiancalendar.h" +#include "provider/islamiccalendar.h" +#endif + +using namespace Qt::StringLiterals; +using SubLabel = CalendarEvents::CalendarEventsPlugin::SubLabel; + +AlternateCalendarPlugin::AlternateCalendarPlugin(QObject *parent) + : CalendarEvents::CalendarEventsPlugin(parent) +{ + auto config = KSharedConfig::openConfig("plasma_calendar_alternatecalendar"_L1, KConfig::NoGlobals); + m_generalConfigGroup = config->group("General"); + m_configWatcher = KConfigWatcher::create(config); + connect(m_configWatcher.get(), &KConfigWatcher::configChanged, this, &AlternateCalendarPlugin::updateSettings); + + init(); +} + +AlternateCalendarPlugin::~AlternateCalendarPlugin() +{ +} + +void AlternateCalendarPlugin::loadEventsForDateRange(const QDate &startDate, const QDate &endDate) +{ + if (!endDate.isValid() || m_calendarSystem == CalendarSystem::Gregorian) { + return; + } + + if (m_lastStartDate == startDate && m_lastEndDate == endDate) { + emitDataChangedSignal(); + return; + } + + switch (m_calendarSystem) { +#if HAVE_ICU + case CalendarSystem::Chinese: + m_provider = new ChineseCalendarProvider(this, m_calendarSystem, startDate, endDate); + break; + case CalendarSystem::Indian: + m_provider = new IndianCalendarProvider(this, m_calendarSystem, startDate, endDate); + break; + case CalendarSystem::Hebrew: + m_provider = new HebrewCalendarProvider(this, m_calendarSystem, startDate, endDate); + break; + case CalendarSystem::Jalali: + case CalendarSystem::Islamic: + case CalendarSystem::IslamicCivil: + case CalendarSystem::IslamicUmalqura: + m_provider = new IslamicCalendarProvider(this, m_calendarSystem, startDate, endDate, m_dateOffset); + break; +#else + // Fall back to QtCalendar + case CalendarSystem::Jalali: + case CalendarSystem::IslamicCivil: +#endif + case CalendarSystem::Julian: + case CalendarSystem::Milankovic: + m_provider = new QtCalendarProvider(this, m_calendarSystem, startDate, endDate, m_dateOffset); + break; + default: + m_provider = new AbstractCalendarProvider(this, m_calendarSystem, startDate, endDate, m_dateOffset); + } + connect(m_provider, + &AbstractCalendarProvider::dataReady, + this, + [this, startDate, endDate](const QHash &alternateDatesData, + const QHash &sublabelData) { + // Check if the sender is the latest + if (m_provider == sender()) { + m_alternateDateCache = alternateDatesData; + m_sublabelCache = sublabelData; + m_lastStartDate = startDate; + m_lastEndDate = endDate; + emitDataChangedSignal(); + m_provider = nullptr; + } + delete sender(); + }); + + m_provider->setAutoDelete(false); + QThreadPool::globalInstance()->start(m_provider); +} + +void AlternateCalendarPlugin::updateSettings(const KConfigGroup &configGroup) +{ + if (configGroup.config()->name() != "plasma_calendar_alternatecalendar"_L1) { + return; + } + init(); + loadEventsForDateRange(m_lastStartDate, m_lastEndDate); +} + +void AlternateCalendarPlugin::init() +{ + m_dateOffset = m_generalConfigGroup.readEntry("dateOffset", 0); + + // Find the matched calendar system + const QString system = m_generalConfigGroup.readEntry("calendarSystem", "Julian"); + const auto systemIt = s_calendarMap.find(system); + + if (systemIt == s_calendarMap.end()) { + // Invalid config, fall back to Gregorian + m_calendarSystem = CalendarSystem::Gregorian; + } else { + m_calendarSystem = systemIt->second.system; + } + + // Clear the old cache when config is reloaded + m_lastStartDate = {}; + m_lastEndDate = {}; + m_alternateDateCache.clear(); + m_sublabelCache.clear(); +} + +void AlternateCalendarPlugin::emitDataChangedSignal() +{ + if (!m_alternateDateCache.empty()) { + Q_EMIT alternateCalendarDateReady(m_alternateDateCache); + } + Q_EMIT subLabelReady(m_sublabelCache); +} + +#include "moc_alternatecalendarplugin.cpp" \ No newline at end of file diff --git a/plasmacalendarplugins/alternatecalendar/alternatecalendarplugin.h b/plasmacalendarplugins/alternatecalendar/alternatecalendarplugin.h new file mode 100644 index 00000000..eeb2d894 --- /dev/null +++ b/plasmacalendarplugins/alternatecalendar/alternatecalendarplugin.h @@ -0,0 +1,56 @@ +/* + SPDX-FileCopyrightText: 2022 Fushan Wen + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef ALTERNATECALENDARPLUGIN_H +#define ALTERNATECALENDARPLUGIN_H + +#include + +#include +#include + +#include + +#include "calendarsystem.h" + +class AbstractCalendarProvider; + +class AlternateCalendarPlugin : public CalendarEvents::CalendarEventsPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.kde.CalendarEventsPlugin" FILE "alternatecalendarplugin.json") + Q_INTERFACES(CalendarEvents::CalendarEventsPlugin) + +public: + AlternateCalendarPlugin(QObject *parent = nullptr); + ~AlternateCalendarPlugin() override; + + void loadEventsForDateRange(const QDate &startDate, const QDate &endDate) override; + +public Q_SLOTS: + void updateSettings(const KConfigGroup &configGroup); + +private: + void init(); + void emitDataChangedSignal(); + + QDate m_lastStartDate; + QDate m_lastEndDate; + AbstractCalendarProvider *m_provider = nullptr; + + // Cache lookup data + QHash m_alternateDateCache; + QHash m_sublabelCache; + + // For updating config + KConfigGroup m_generalConfigGroup; + KConfigWatcher::Ptr m_configWatcher; + + CalendarSystem::System m_calendarSystem = CalendarSystem::Gregorian; + int m_dateOffset = 0; // For the (tabular) Islamic Civil calendar +}; + +#endif diff --git a/plasmacalendarplugins/alternatecalendar/alternatecalendarplugin.json b/plasmacalendarplugins/alternatecalendar/alternatecalendarplugin.json new file mode 100644 index 00000000..fa472482 --- /dev/null +++ b/plasmacalendarplugins/alternatecalendar/alternatecalendarplugin.json @@ -0,0 +1,92 @@ +{ + "KPlugin": { + "Description": "Calendar plugin for displaying dates from the alternate calendar system", + "Description[ar]": "ملحق تقويم يعرض التاريخ في نظام تقوم بديل", + "Description[az]": "Alternativ təqvim sistemindən tarixləri göstərmək üçün təqvim qoşması", + "Description[bg]": "Приставка на календара за показване на дати от алтернативни календарни системи", + "Description[ca@valencia]": "Connector de calendari per a mostrar dates del sistema alternatiu de calendari", + "Description[ca]": "Connector de calendari per a mostrar dates del sistema alternatiu de calendari", + "Description[cs]": "Modul kalendáře pro zobrazování událostí z alternativního kalendářového systému", + "Description[da]": "Kalendarplugin, der viser datoer fra det alternative kalendersystem", + "Description[de]": "Kalender-Modul zur Anzeige von Daten aus dem alternativen Kalendersystem", + "Description[en_GB]": "Calendar plugin for displaying dates from the alternate calendar system", + "Description[eo]": "Kalendara kromaĵo por montri datojn de la alterna kalendara sistemo", + "Description[es]": "Complemento del calendario para mostrar fechas del sistema de calendario alternativo", + "Description[eu]": "Ordezko egutegi sistemako datak azaltzeko egutegiko plugina", + "Description[fi]": "Kalenteriliitännäinen päivyrin näyttämiseksi vaihtoehtoisella kalenterijärjestelmällä", + "Description[fr]": "Module externe d'agenda pour l'affichage de dates à partir d'un système alternatif d'agenda", + "Description[gl]": "Complemento de calendario para amosar datas dun sistema de calendario alternativo", + "Description[he]": "תוסף לוח שנה להצגת תאריכים ממערכות לוחות שנה חלופיות", + "Description[hu]": "Naptár bővítmény az alternatív naptárrendszerből származó dátumok megjelenítéséhez", + "Description[ia]": "Plugin de calendario per monstrar datas ex le systema alternate de calendario", + "Description[id]": "Plugin kalender untuk menampilkan tanggal dari sistem kalender alternatif", + "Description[is]": "Dagatalsviðbót til að birta dagsetningar úr öðru tímatalskerfi", + "Description[it]": "Estensione del calendario per la visualizzazione delle date dal sistema di calendario alternativo", + "Description[ja]": "代替カレンダーからの日付を表示するカレンダープラグイン", + "Description[ka]": "კალენდარის დამატება თარიღების ალტერნატიული კალენდარის სისტემიდან საჩვენებლად", + "Description[ko]": "대체 달력의 날짜를 표시하는 달력 플러그인", + "Description[lt]": "Kalendoriaus įskiepis, skirtas atvaizduoti datas iš alternatyvios kalendorinės sistemos", + "Description[lv]": "Kalendāra spraudnis lai parādītu datumus no alternatīvas kalendāra sistēmas", + "Description[nl]": "Plug-in voor agenda voor tonen van afspraken uit het alternatieve agendasysteem", + "Description[nn]": "Kalendertillegg for vising av datoar i alternative kalendersystem", + "Description[pl]": "Wtyczka kalendarza do wyświetlania dat z zastępczego kalendarza", + "Description[pt]": "'Plugin' de calendário para mostrar as datas de um sistema de calendário alternativo", + "Description[pt_BR]": "Plugin de calendário para mostrar eventos de sistema de calendários alternativo", + "Description[ro]": "Extensie de calendar pentru afișarea datelor din sisteme calendaristice alternative", + "Description[ru]": "Модуль календаря для отображения альтернативных календарных систем", + "Description[sk]": "Zásuvný modul kalendára na zobrazovanie dátumov z alternatívneho systému kalendára", + "Description[sl]": "Koledarski vtičnik za prikaz datumov alternativnih koledarskih sistemov", + "Description[sv]": "Kalenderinsticksprogram för att visa datum från det alternativa kalendersystemet", + "Description[tr]": "Alternatif takvim sisteminden tarihleri görüntülemek için takvim eklentisi", + "Description[uk]": "Додаток календаря для показу дат із альтернативної календарної системи", + "Description[vi]": "Phần cài cắm lịch để hiển thị ngày ở một hệ lịch khác", + "Description[x-test]": "xxCalendar plugin for displaying dates from the alternate calendar systemxx", + "Description[zh_CN]": "用于显示特定文化历法的日期的日历插件", + "Description[zh_TW]": "用於顯示替代曆法日期的行事曆外掛程式", + "Icon": "office-calendar", + "Name": "Alternate Calendar", + "Name[ar]": "التقويم البديل", + "Name[az]": "Alternativ təqvim", + "Name[bg]": "Алтернативен календар", + "Name[ca@valencia]": "Calendari alternatiu", + "Name[ca]": "Calendari alternatiu", + "Name[cs]": "Alternativní kalendář", + "Name[da]": "Alternativ kalender", + "Name[de]": "Alternativer Kalender", + "Name[en_GB]": "Alternate Calendar", + "Name[eo]": "Alterna Kalendaro", + "Name[es]": "Calendario alternativo", + "Name[eu]": "Ordezko egutegia", + "Name[fi]": "Vaihtoehtoinen kalenteri", + "Name[fr]": "Agenda alternatif", + "Name[gl]": "Calendario alternativo", + "Name[he]": "יומן חלופי", + "Name[hu]": "Alternatív naptár", + "Name[ia]": "Calendario alterne", + "Name[id]": "Alternate Calendar", + "Name[is]": "Annað dagatal", + "Name[it]": "Calendario alternativo", + "Name[ja]": "代替カレンダー", + "Name[ka]": "ალტერნატიული კალენდარი", + "Name[ko]": "대체 달력", + "Name[lt]": "Alternatyvus kalendorius", + "Name[lv]": "Alternatīvs kalendārs", + "Name[nl]": "Alternatieve agenda", + "Name[nn]": "Alternativ kalender", + "Name[pl]": "Zastępczy kalendarz", + "Name[pt]": "Calendário Alternativo", + "Name[pt_BR]": "Calendário alternativo", + "Name[ro]": "Calendar alternativ", + "Name[ru]": "Альтернативные календарные системы", + "Name[sk]": "Alternatívny kalendár", + "Name[sl]": "Alternativni koledar", + "Name[sv]": "Alternativ kalender", + "Name[tr]": "Alternatif Takvim", + "Name[uk]": "Альтернативний календар", + "Name[vi]": "Lịch khác", + "Name[x-test]": "xxAlternate Calendarxx", + "Name[zh_CN]": "特定文化日历", + "Name[zh_TW]": "替代曆法" + }, + "X-KDE-PlasmaCalendar-ConfigUi": "alternatecalendar/AlternateCalendarConfig.qml" +} diff --git a/plasmacalendarplugins/alternatecalendar/calendarsystem.h b/plasmacalendarplugins/alternatecalendar/calendarsystem.h new file mode 100644 index 00000000..1d45a720 --- /dev/null +++ b/plasmacalendarplugins/alternatecalendar/calendarsystem.h @@ -0,0 +1,85 @@ +/* + SPDX-FileCopyrightText: 2022 Fushan Wen + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include + +#include +#include + +#include + +namespace CalendarSystem +{ +Q_NAMESPACE + +/** + * @note When adding a new calendar system, \s_calendarMap should also be updated. + */ +enum System : int { + Gregorian = static_cast(QCalendar::System::Gregorian), +#ifndef QT_BOOTSTRAPPED + Julian = static_cast(QCalendar::System::Julian), + Milankovic = static_cast(QCalendar::System::Milankovic), +#endif + Chinese = static_cast(QCalendar::System::Last) + 1, // QTBUG-89824: QCalendar doesn't support Chinese calendar system + Indian, // QCalendar doesn't support India calendar system + Hebrew, + Jalali, // Persian + Islamic, // Astronomical + IslamicCivil, + IslamicUmalqura, +}; +Q_ENUM_NS(System) +}; + +struct CalendarSystemItem { + const CalendarSystem::System system; + const QString id; + const QString text; +}; + +// clang-format off +static const std::map s_calendarMap{ + { + QStringLiteral("Julian"), + {CalendarSystem::Julian, QStringLiteral("Julian"), i18ndc("plasma_calendar_alternatecalendar", "@item:inlist", "Julian")} + }, + { + QStringLiteral("Milankovic"), + {CalendarSystem::Milankovic, QStringLiteral("Milankovic"), i18ndc("plasma_calendar_alternatecalendar", "@item:inlist", "Milankovic")} + }, + { + QStringLiteral("Jalali"), + {CalendarSystem::Jalali, QStringLiteral("Jalali"), i18ndc("plasma_calendar_alternatecalendar", "@item:inlist", "The Solar Hijri Calendar (Persian)")} + }, + { + QStringLiteral("Islamic"), + {CalendarSystem::Islamic, QStringLiteral("Islamic"), i18ndc("plasma_calendar_alternatecalendar", "@item:inlist See https://cldr.unicode.org/development/development-process/design-proposals/islamic-calendar-types for more details", "Islamic Calendar (Astronomical)")} + }, + { + QStringLiteral("IslamicCivil"), + {CalendarSystem::IslamicCivil, QStringLiteral("IslamicCivil"), i18ndc("plasma_calendar_alternatecalendar", "@item:inlist See https://cldr.unicode.org/development/development-process/design-proposals/islamic-calendar-types for more details", "The Islamic Civil Calendar (Tabular)")} + }, + { + QStringLiteral("IslamicUmalqura"), + {CalendarSystem::IslamicUmalqura, QStringLiteral("IslamicUmalqura"), i18ndc("plasma_calendar_alternatecalendar", "@item:inlist See https://cldr.unicode.org/development/development-process/design-proposals/islamic-calendar-types for more details", "Islamic Calendar (Umm al-Qura)")} + }, + { + QStringLiteral("Chinese"), + {CalendarSystem::Chinese, QStringLiteral("Chinese"), i18ndc("plasma_calendar_alternatecalendar", "@item:inlist", "Chinese Lunar Calendar")} + }, + { + QStringLiteral("Indian"), + {CalendarSystem::Indian, QStringLiteral("Indian"), i18ndc("plasma_calendar_alternatecalendar", "@item:inlist", "Indian National Calendar")} + }, + { + QStringLiteral("Hebrew"), + {CalendarSystem::Hebrew, QStringLiteral("Hebrew"), i18ndc("plasma_calendar_alternatecalendar", "@item:inlist", "Hebrew Calendar")} + }, +}; +// clang-format on diff --git a/plasmacalendarplugins/alternatecalendar/config-ICU.h.cmake b/plasmacalendarplugins/alternatecalendar/config-ICU.h.cmake new file mode 100644 index 00000000..bf97d6ae --- /dev/null +++ b/plasmacalendarplugins/alternatecalendar/config-ICU.h.cmake @@ -0,0 +1,3 @@ +#pragma once + +#cmakedefine01 HAVE_ICU diff --git a/plasmacalendarplugins/alternatecalendar/config/CMakeLists.txt b/plasmacalendarplugins/alternatecalendar/config/CMakeLists.txt new file mode 100644 index 00000000..4dbb2295 --- /dev/null +++ b/plasmacalendarplugins/alternatecalendar/config/CMakeLists.txt @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: 2022 Fushan Wen + +# SPDX-License-Identifier: GPL-2.0-or-later + +add_subdirectory(qml) +add_subdirectory(plugin) diff --git a/plasmacalendarplugins/alternatecalendar/config/plugin/CMakeLists.txt b/plasmacalendarplugins/alternatecalendar/config/plugin/CMakeLists.txt new file mode 100644 index 00000000..1d010a45 --- /dev/null +++ b/plasmacalendarplugins/alternatecalendar/config/plugin/CMakeLists.txt @@ -0,0 +1,26 @@ +# SPDX-FileCopyrightText: 2022 Fushan Wen +# SPDX-FileCopyrightText: 2024 ivan tkachenko +# +# SPDX-License-Identifier: BSD-3-Clause + +ecm_add_qml_module(plasmacalendaralternatecalendarconfig + URI org.kde.plasma.private.alternatecalendarconfig + GENERATE_PLUGIN_SOURCE +) + +target_sources(plasmacalendaralternatecalendarconfig + PRIVATE + ../../calendarsystem.h + configstorage.cpp + configstorage.h +) + +target_link_libraries(plasmacalendaralternatecalendarconfig + PRIVATE + Qt::Qml + Qt::Core + KF6::ConfigCore + KF6::I18n +) + +ecm_finalize_qml_module(plasmacalendaralternatecalendarconfig) diff --git a/plasmacalendarplugins/alternatecalendar/config/plugin/configstorage.cpp b/plasmacalendarplugins/alternatecalendar/config/plugin/configstorage.cpp new file mode 100644 index 00000000..5e448f23 --- /dev/null +++ b/plasmacalendarplugins/alternatecalendar/config/plugin/configstorage.cpp @@ -0,0 +1,109 @@ +/* + SPDX-FileCopyrightText: 2018 Friedrich W. H. Kossebau + SPDX-FileCopyrightText: 2022 Fushan Wen + SPDX-FileCopyrightText: 2024 ivan tkachenko + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "configstorage.h" + +#include + +#include + +CalendarSystemModel::CalendarSystemModel(QObject *parent) + : QAbstractListModel(parent) +{ + const QMetaEnum e = QMetaEnum::fromType(); + + beginInsertRows(QModelIndex(), 0, e.keyCount() - 2 /* Gregorian */); + + m_items.reserve(e.keyCount() - 1); + for (int k = 0; k < e.keyCount(); k++) { + const auto system = static_cast(e.value(k)); + + auto it = std::find_if(s_calendarMap.cbegin(), s_calendarMap.cend(), [system](const std::pair &pr) { + return pr.second.system == system; + }); + + if (it != s_calendarMap.cend()) { + m_items.emplace_back(it->second); + } + } + + endInsertRows(); +} + +QVariant CalendarSystemModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + + const auto &item = m_items.at(index.row()); + + switch (role) { + case Qt::DisplayRole: + return item.text; + case IdRole: + return item.id; + default: + return QVariant(); + } +} + +int CalendarSystemModel::rowCount(const QModelIndex &parent) const +{ + return parent.isValid() ? 0 : m_items.size(); +} + +QHash CalendarSystemModel::roleNames() const +{ + return { + {Qt::DisplayRole, QByteArrayLiteral("display")}, + {IdRole, QByteArrayLiteral("id")}, + }; +} + +int CalendarSystemModel::indexOf(const QString &id) const +{ + const auto it = std::find_if(m_items.cbegin(), m_items.cend(), [&id](const CalendarSystemItem &item) { + return item.id == id; + }); + + if (it != m_items.cend()) { + return std::distance(m_items.cbegin(), it); + } + + return 0; +} + +ConfigStorage::ConfigStorage(QObject *parent) + : QObject(parent) + , m_calendarSystemModel(new CalendarSystemModel(this)) +{ + auto config = KSharedConfig::openConfig(QStringLiteral("plasma_calendar_alternatecalendar")); + m_generalConfigGroup = config->group("General"); + + m_calendarSystem = m_generalConfigGroup.readEntry("calendarSystem", "Gregorian"); + m_dateOffset = m_generalConfigGroup.readEntry("dateOffset", 0); +} + +CalendarSystemModel *ConfigStorage::calendarSystemModel() const +{ + return m_calendarSystemModel; +} + +int ConfigStorage::currentIndex() const +{ + return m_calendarSystemModel->indexOf(m_calendarSystem); +} + +void ConfigStorage::save() +{ + m_generalConfigGroup.writeEntry("calendarSystem", m_calendarSystem, KConfigBase::Notify); + m_generalConfigGroup.writeEntry("dateOffset", m_dateOffset, KConfigBase::Notify); + + m_generalConfigGroup.sync(); +} diff --git a/plasmacalendarplugins/alternatecalendar/config/plugin/configstorage.h b/plasmacalendarplugins/alternatecalendar/config/plugin/configstorage.h new file mode 100644 index 00000000..005491ba --- /dev/null +++ b/plasmacalendarplugins/alternatecalendar/config/plugin/configstorage.h @@ -0,0 +1,94 @@ +/* + SPDX-FileCopyrightText: 2018 Friedrich W. H. Kossebau + SPDX-FileCopyrightText: 2022 Fushan Wen + SPDX-FileCopyrightText: 2024 ivan tkachenko + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef CONFIGSTORAGE_H +#define CONFIGSTORAGE_H + +#include +#include + +#include + +#include "../../calendarsystem.h" + +class CalendarSystemModel : public QAbstractListModel +{ + Q_OBJECT + QML_ELEMENT + +public: + enum Role { + IdRole = Qt::UserRole, + }; + + explicit CalendarSystemModel(QObject *parent = nullptr); + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QHash roleNames() const override; + + int indexOf(const QString &id) const; + +private: + std::vector m_items; +}; + +class ConfigStorage : public QObject +{ + Q_OBJECT + QML_ELEMENT + + /** + * The current choosen calendar system + */ + Q_PROPERTY(QString calendarSystem MEMBER m_calendarSystem NOTIFY calendarSystemChanged) + + /** + * The available calendar system list + */ + Q_PROPERTY(CalendarSystemModel *calendarSystemModel READ calendarSystemModel CONSTANT) + + /** + * The index of current choosen calendar system + */ + Q_PROPERTY(int currentIndex READ currentIndex CONSTANT) + + /** + * The date offset in days + */ + Q_PROPERTY(int dateOffset MEMBER m_dateOffset NOTIFY dateOffsetChanged) + +public: + explicit ConfigStorage(QObject *parent = nullptr); + + CalendarSystemModel *calendarSystemModel() const; + + int currentIndex() const; + + /** + * Saves the modifed configuration. + * + * @see calendarSystem + * @see dateOffset + */ + Q_INVOKABLE void save(); + +Q_SIGNALS: + void calendarSystemChanged(); + void dateOffsetChanged(); + +private: + KConfigGroup m_generalConfigGroup; + + QString m_calendarSystem; + CalendarSystemModel *const m_calendarSystemModel; + + int m_dateOffset; // For the (tabular) Islamic Civil calendar +}; + +#endif diff --git a/plasmacalendarplugins/alternatecalendar/config/qml/AlternateCalendarConfig.qml b/plasmacalendarplugins/alternatecalendar/config/qml/AlternateCalendarConfig.qml new file mode 100644 index 00000000..843f41b5 --- /dev/null +++ b/plasmacalendarplugins/alternatecalendar/config/qml/AlternateCalendarConfig.qml @@ -0,0 +1,88 @@ +/* + SPDX-FileCopyrightText: 2018 Friedrich W. H. Kossebau + SPDX-FileCopyrightText: 2022 Fushan Wen + SPDX-FileCopyrightText: 2024 ivan tkachenko + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +import QtQuick +import QtQuick.Controls as QQC2 +import QtQuick.Templates as T + +import org.kde.kcmutils as KCMUtils +import org.kde.kirigami as Kirigami + +import org.kde.plasma.private.alternatecalendarconfig as Private + +KCMUtils.SimpleKCM { + id: configPage + + // expected API + signal configurationChanged() + + // expected API + function saveConfig() { + configStorage.calendarSystem = calendarSystemComboBox.currentValue; + configStorage.dateOffset = (dateOffsetSpinBoxLoader.item as T.SpinBox)?.value ?? 0; + + configStorage.save(); + } + + Kirigami.FormLayout { + + Private.ConfigStorage { + id: configStorage + } + + Row { + Kirigami.FormData.label: i18ndc("plasma_calendar_alternatecalendar", "@label:listbox", "Calendar system:") + + QQC2.ComboBox { + id: calendarSystemComboBox + model: configStorage.calendarSystemModel + textRole: "display" + valueRole: "id" + Accessible.name: currentText + Accessible.onPressAction: { + currentIndex = currentIndex == count - 1 ? 0 : currentIndex + 1; + configPage.configurationChanged(); + } + onActivated: configPage.configurationChanged() + Component.onCompleted: { + currentIndex = configStorage.currentIndex; + } + } + + Kirigami.ContextualHelpButton { + anchors.verticalCenter: calendarSystemComboBox.verticalCenter + visible: calendarSystemComboBox.currentValue === "Islamic" + toolTipText: i18ndc("plasma_calendar_alternatecalendar", "@info:tooltip", "This calendar is based on pure astronomical calculation. It doesn't consider any crescent visibility criteria.") + } + } + + Loader { + id: dateOffsetSpinBoxLoader + active: calendarSystemComboBox.currentValue.startsWith("Islamic") + visible: active + Kirigami.FormData.label: i18ndc("plasma_calendar_alternatecalendar", "@label:spinbox", "Date offset:") + + sourceComponent: QQC2.SpinBox { + hoverEnabled: true + + stepSize: 1 + from: -10 + to: 10 + value: configStorage.dateOffset + onValueModified: configPage.configurationChanged() + + textFromValue: (value, locale) => i18ndp("plasma_calendar_alternatecalendar","%1 day", "%1 days", value) + valueFromText: (text, locale) => parseInt(text) + + QQC2.ToolTip.text: i18ndc("plasma_calendar_alternatecalendar", "@info:tooltip", "A positive offset signifies a later date, while a negative offset signifies an earlier date.") + QQC2.ToolTip.visible: hovered + QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay + } + } + } +} diff --git a/plasmacalendarplugins/alternatecalendar/config/qml/CMakeLists.txt b/plasmacalendarplugins/alternatecalendar/config/qml/CMakeLists.txt new file mode 100644 index 00000000..6f9bed90 --- /dev/null +++ b/plasmacalendarplugins/alternatecalendar/config/qml/CMakeLists.txt @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: 2022 Fushan Wen + +# SPDX-License-Identifier: GPL-2.0-or-later + +install(FILES AlternateCalendarConfig.qml + DESTINATION ${KDE_INSTALL_PLUGINDIR}/plasmacalendarplugins/alternatecalendar) diff --git a/plasmacalendarplugins/alternatecalendar/provider/abstractcalendarprovider.cpp b/plasmacalendarplugins/alternatecalendar/provider/abstractcalendarprovider.cpp new file mode 100644 index 00000000..5c93a706 --- /dev/null +++ b/plasmacalendarplugins/alternatecalendar/provider/abstractcalendarprovider.cpp @@ -0,0 +1,60 @@ +/* + SPDX-FileCopyrightText: 2022 Fushan Wen + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "abstractcalendarprovider.h" + +AbstractCalendarProvider::AbstractCalendarProvider(QObject *parent, + CalendarSystem::System calendarSystem, + const QDate &startDate, + const QDate &endDate, + int dateOffset) + : QObject(parent) + , m_calendarSystem(calendarSystem) + , m_startDate(startDate) + , m_endDate(endDate) + , m_dateOffset(dateOffset) +{ +} + +AbstractCalendarProvider::~AbstractCalendarProvider() +{ +} + +QCalendar::YearMonthDay AbstractCalendarProvider::fromGregorian(const QDate &date) const +{ + if (!date.isValid()) { + return {}; + } + + return QCalendar::YearMonthDay(date.year(), date.month(), date.day()); +} + +CalendarEvents::CalendarEventsPlugin::SubLabel AbstractCalendarProvider::subLabel([[maybe_unused]] const QDate &date) const +{ + auto sublabel = CalendarEvents::CalendarEventsPlugin::SubLabel{}; + sublabel.priority = CalendarEvents::CalendarEventsPlugin::SubLabelPriority::Low; + + return sublabel; +} + +void AbstractCalendarProvider::run() +{ + static_assert(std::is_trivially_copy_assignable_v); + static_assert(std::is_trivially_move_assignable_v); + + QHash alternateDatesData; + QHash sublabelData; + for (QDate date = m_startDate; date <= m_endDate && date.isValid(); date = date.addDays(1)) { + const QDate offsetDate = date.addDays(m_dateOffset); + const QCalendar::YearMonthDay alt = fromGregorian(offsetDate); + alternateDatesData.emplace(date, alt.year, alt.month, alt.day); + sublabelData.emplace(date, subLabel(offsetDate)); + } + + Q_EMIT dataReady(alternateDatesData, sublabelData); +} + +#include "moc_abstractcalendarprovider.cpp" \ No newline at end of file diff --git a/plasmacalendarplugins/alternatecalendar/provider/abstractcalendarprovider.h b/plasmacalendarplugins/alternatecalendar/provider/abstractcalendarprovider.h new file mode 100644 index 00000000..16c0680d --- /dev/null +++ b/plasmacalendarplugins/alternatecalendar/provider/abstractcalendarprovider.h @@ -0,0 +1,62 @@ +/* + SPDX-FileCopyrightText: 2022 Fushan Wen + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include +#include +#include +#include + +#include + +#include "calendarsystem.h" + +/** + * @short An abstract base class for alternate calendar providers. + * + * This class serves as abstract base class for alternate calendar + * provider implementations. + */ +class AbstractCalendarProvider : public QObject, public QRunnable +{ + Q_OBJECT + +public: + explicit AbstractCalendarProvider(QObject *parent, CalendarSystem::System calendarSystem, const QDate &startDate, const QDate &endDate, int dateOffset = 0); + ~AbstractCalendarProvider() override; + + /** + * Converts Gregorian date to alternate calendar date + * + * @param date Gregorian date + * @return Alternate calendar date + */ + virtual QCalendar::YearMonthDay fromGregorian(const QDate &date) const; + + /** + * Gets the sub-labels for the specific Gregorian date + * + * @param date Gregorian date + * @return A sublabel for the specific Gregorian date + */ + virtual CalendarEvents::CalendarEventsPlugin::SubLabel subLabel(const QDate &date) const; + + /** + * Loads calendar data in date list @p dates with given offset @p dateOffset + */ + void run() override; + +Q_SIGNALS: + void dataReady(const QHash &alternateDatesData, + const QHash &sublabelData); + +protected: + const CalendarSystem::System m_calendarSystem; + const QDate m_startDate; + const QDate m_endDate; + const int m_dateOffset; +}; diff --git a/plasmacalendarplugins/alternatecalendar/provider/chinesecalendar.cpp b/plasmacalendarplugins/alternatecalendar/provider/chinesecalendar.cpp new file mode 100644 index 00000000..80a484c6 --- /dev/null +++ b/plasmacalendarplugins/alternatecalendar/provider/chinesecalendar.cpp @@ -0,0 +1,229 @@ +/* + SPDX-FileCopyrightText: 2021 Gary Wang + SPDX-FileCopyrightText: 2022 Fushan Wen + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "chinesecalendar.h" + +#include +#include + +#include "icucalendar_p.h" +#include "solarutils.h" + +using namespace Qt::StringLiterals; + +namespace +{ +std::shared_mutex s_solarTermsMapMutex; +using SolarTermsMap = QHash>; +constinit SolarTermsMap s_solarTermsMap; +const std::array s_solarTermNames{ + u"春分"_s, u"清明"_s, u"谷雨"_s, u"立夏"_s, u"小满"_s, u"芒种"_s, u"夏至"_s, u"小暑"_s, u"大暑"_s, u"立秋"_s, u"处暑"_s, u"白露"_s, + u"秋分"_s, u"寒露"_s, u"霜降"_s, u"立冬"_s, u"小雪"_s, u"大雪"_s, u"冬至"_s, u"小寒"_s, u"大寒"_s, u"立春"_s, u"雨水"_s, u"惊蛰"_s, +}; +} + +class ChineseCalendarProviderPrivate : public ICUCalendarPrivate +{ +public: + explicit ChineseCalendarProviderPrivate(); + + CalendarEvents::CalendarEventsPlugin::SubLabel subLabel(const QDate &date); + +private: + enum SolarTerm { + ChunFen = 0, + QingMing, + GuYu, + LiXia, + XiaoMan, + MangZhong, + XiaZhi, + XiaoShu, + DaShu, + LiQiu, + ChuShu, + BaiLu, + QiuFen, + HanLu, + ShuangJiang, + LiDong, + XiaoXue, + DaXue, + DongZhi, + XiaoHan, + DaHan, + LiChun, + YuShui, + JingZhe, + }; + + /** + * For formatting, see the documentation of SimpleDateFormat: + * https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classicu_1_1SimpleDateFormat.html#details + */ + QString formattedDateString(const icu::UnicodeString &str, bool hanidays = false) const; + + QString yearDisplayName() const; + QString monthDisplayName() const; + QString dayDisplayName() const; + + SolarTermsMap::iterator generateSolarTermsCache(int year); + + /** + * Calculates Julian day of a solar term based on Newton's method + */ + QDate getSolarTermDate(int year, SolarTerm order) const; + + icu::Locale m_locale; + icu::Locale m_hanidaysLocale; +}; + +ChineseCalendarProviderPrivate::ChineseCalendarProviderPrivate() + : ICUCalendarPrivate() + , m_locale(icu::Locale("zh", 0, 0, "calendar=chinese")) + , m_hanidaysLocale(icu::Locale("zh", 0, 0, "calendar=chinese;numbers=hanidays")) +{ + if (U_FAILURE(m_errorCode)) { + return; // Failed to create m_GregorianCalendar + } + + m_calendar.reset(icu::Calendar::createInstance("en_US@calendar=chinese", m_errorCode)); +} + +SolarTermsMap::iterator ChineseCalendarProviderPrivate::generateSolarTermsCache(int year) +{ +#if __has_cpp_attribute(assume) + [[assume(year > 0)]]; +#endif + { + std::shared_lock lock(s_solarTermsMapMutex); + auto thisYearIt = s_solarTermsMap.find(year); + if (thisYearIt != s_solarTermsMap.end()) { + // Already generated + return thisYearIt; + } + } + + std::unique_lock lock(s_solarTermsMapMutex); + auto thisYearIt = s_solarTermsMap.insert(year, {}); + + SolarTerm solarTermIndex = DongZhi; + for (int i = 0; i < 25; i++) { + (*thisYearIt)[i] = getSolarTermDate(year - 1, solarTermIndex); + if (solarTermIndex == DongZhi) { + year++; + } + solarTermIndex = static_cast((solarTermIndex + 1) % 24); + } + + return thisYearIt; +} + +CalendarEvents::CalendarEventsPlugin::SubLabel ChineseCalendarProviderPrivate::subLabel(const QDate &date) +{ + auto sublabel = CalendarEvents::CalendarEventsPlugin::SubLabel{}; + + if (U_FAILURE(m_errorCode) || !date.isValid() || !setDate(date)) { + return sublabel; + } + + sublabel.yearLabel = yearDisplayName(); + sublabel.monthLabel = monthDisplayName(); + + // Check solar term cache exists + auto it = generateSolarTermsCache(date.year()); + + int solarTermIndex = -1; + { + const int indexInList = 2 * date.month() - 1; + const QDate date1 = it->at(indexInList); + const QDate date2 = it->at(indexInList + 1); + if (date1.day() == date.day()) { + solarTermIndex = (indexInList + DongZhi) % 24; + } else if (date2.day() == date.day()) { + solarTermIndex = (indexInList + 1 + DongZhi) % 24; + } + } + + const QString dayName = dayDisplayName(); + sublabel.dayLabel = day() == 1 ? monthDisplayName() : (solarTermIndex >= 0 ? s_solarTermNames.at(solarTermIndex) : dayName); + const QString solarTerm = solarTermIndex >= 0 ? QStringLiteral(" (%1)").arg(s_solarTermNames.at(solarTermIndex)) : QString(); + sublabel.label = QStringLiteral("%1%2%3%4").arg(sublabel.yearLabel, sublabel.monthLabel, dayName, solarTerm); + sublabel.priority = CalendarEvents::CalendarEventsPlugin::SubLabelPriority::Low; + + return sublabel; +} + +QString ChineseCalendarProviderPrivate::formattedDateString(const icu::UnicodeString &str, bool hanidays) const +{ + UErrorCode errorCode = U_ZERO_ERROR; + icu::UnicodeString dateString; + icu::SimpleDateFormat formatter(str, hanidays ? m_hanidaysLocale : m_locale, errorCode); + formatter.setCalendar(*m_calendar); + formatter.format(m_calendar->getTime(errorCode), dateString); + + return QStringView(dateString.getBuffer(), dateString.length()).toString(); +} + +QString ChineseCalendarProviderPrivate::yearDisplayName() const +{ + return formattedDateString("U"); +} + +QString ChineseCalendarProviderPrivate::monthDisplayName() const +{ + return formattedDateString("MMM"); +} + +QString ChineseCalendarProviderPrivate::dayDisplayName() const +{ + return formattedDateString("d", true); +} + +QDate ChineseCalendarProviderPrivate::getSolarTermDate(int year, SolarTerm order) const +{ +#if __has_cpp_attribute(assume) + [[assume(year > 0)]]; +#endif + constexpr double RADIANS_PER_TERM = std::numbers::pi / 12.0; + double angle = double(order) * RADIANS_PER_TERM; + int month = ((order + 1) / 2 + 2) % 12 + 1; + // 春分: Mar 20th + int day = 6; + if (order % 2 == 0) { + day = 20; + } + const int64_t initialJulianDay = SolarUtils::toJulianDay(year, month, day); + // Can't use QDate::toJulianDay because it doesn't support extra hours + double julianDay = SolarUtils::NewtonIteration(angle, initialJulianDay); + // To UTC+8 time + julianDay += 8.0 / 24.0; + + int resultYear, resultMonth, resultDay; + SolarUtils::getDateFromJulianDay(julianDay, resultYear, resultMonth, resultDay); + // TT -> UTC + julianDay -= SolarUtils::getDeltaT(resultYear, resultMonth) / 86400; + SolarUtils::getDateFromJulianDay(julianDay, resultYear, resultMonth, resultDay); + + return QDate(resultYear, resultMonth, resultDay); +} + +ChineseCalendarProvider::ChineseCalendarProvider(QObject *parent, CalendarSystem::System calendarSystem, const QDate &startDate, const QDate &endDate) + : AbstractCalendarProvider(parent, calendarSystem, startDate, endDate) + , d(std::make_unique()) +{ + Q_ASSERT(m_calendarSystem == CalendarSystem::Chinese); +} + +ChineseCalendarProvider::~ChineseCalendarProvider() +{ +} + +CalendarEvents::CalendarEventsPlugin::SubLabel ChineseCalendarProvider::subLabel(const QDate &date) const +{ + return d->subLabel(date); +} diff --git a/plasmacalendarplugins/alternatecalendar/provider/chinesecalendar.h b/plasmacalendarplugins/alternatecalendar/provider/chinesecalendar.h new file mode 100644 index 00000000..e74853e2 --- /dev/null +++ b/plasmacalendarplugins/alternatecalendar/provider/chinesecalendar.h @@ -0,0 +1,29 @@ +/* + SPDX-FileCopyrightText: 2021 Gary Wang + SPDX-FileCopyrightText: 2022 Fushan Wen + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include "abstractcalendarprovider.h" + +/** + * @short An alternate calendar provider for Chinese calendar system. + * + * This class presents an alternate calendar provider for Chinese calendar system. + */ +class ChineseCalendarProvider : public AbstractCalendarProvider +{ + Q_OBJECT + +public: + explicit ChineseCalendarProvider(QObject *parent, CalendarSystem::System calendarSystem, const QDate &startDate, const QDate &endDate); + ~ChineseCalendarProvider() override; + + CalendarEvents::CalendarEventsPlugin::SubLabel subLabel(const QDate &date) const override; + +private: + const std::unique_ptr d; +}; diff --git a/plasmacalendarplugins/alternatecalendar/provider/hebrewcalendar.cpp b/plasmacalendarplugins/alternatecalendar/provider/hebrewcalendar.cpp new file mode 100644 index 00000000..74f70d79 --- /dev/null +++ b/plasmacalendarplugins/alternatecalendar/provider/hebrewcalendar.cpp @@ -0,0 +1,120 @@ +/* + SPDX-FileCopyrightText: 2023 Fushan Wen + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "hebrewcalendar.h" + +#include "icucalendar_p.h" + +class HebrewCalendarProviderPrivate : public ICUCalendarPrivate +{ +public: + explicit HebrewCalendarProviderPrivate(); + + /** + * For formatting, see the documentation of SimpleDateFormat: + * https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classicu_1_1SimpleDateFormat.html#details + */ + QString formattedDateString(const icu::UnicodeString &str) const; + QString formattedDateStringInNativeLanguage(const icu::UnicodeString &str) const; + + QCalendar::YearMonthDay fromGregorian(const QDate &_date); + CalendarEvents::CalendarEventsPlugin::SubLabel subLabel(const QDate &date); + +private: + // See https://unicode-org.github.io/icu/userguide/locale/#keywords for available keywords + icu::Locale m_hebrewLocale; + icu::Locale m_nativeLocale; +}; + +HebrewCalendarProviderPrivate::HebrewCalendarProviderPrivate() + : ICUCalendarPrivate() + , m_hebrewLocale(icu::Locale("he_IL", 0, 0, "calendar=hebrew;numbers=hebr")) + , m_nativeLocale(icu::Locale(QLocale::system().name().toLatin1(), 0, 0, "calendar=hebrew;numbers=hebr")) +{ + if (U_FAILURE(m_errorCode)) { + return; // Failed to create m_GregorianCalendar + } + + m_calendar.reset(icu::Calendar::createInstance(icu::Locale("he_IL@calendar=hebrew"), m_errorCode)); +} + +QString HebrewCalendarProviderPrivate::formattedDateString(const icu::UnicodeString &str) const +{ + UErrorCode errorCode = U_ZERO_ERROR; + icu::UnicodeString dateString; + icu::SimpleDateFormat formatter(str, m_hebrewLocale, errorCode); + formatter.setCalendar(*m_calendar); + formatter.format(m_calendar->getTime(errorCode), dateString); + + return QStringView(dateString.getBuffer(), dateString.length()).toString(); +} + +QString HebrewCalendarProviderPrivate::formattedDateStringInNativeLanguage(const icu::UnicodeString &str) const +{ + UErrorCode errorCode = U_ZERO_ERROR; + icu::UnicodeString dateString; + icu::SimpleDateFormat formatter(str, m_nativeLocale, errorCode); + formatter.setCalendar(*m_calendar); + formatter.format(m_calendar->getTime(errorCode), dateString); + + return QStringView(dateString.getBuffer(), dateString.length()).toString(); +} + +QCalendar::YearMonthDay HebrewCalendarProviderPrivate::fromGregorian(const QDate &_date) +{ + if (U_FAILURE(m_errorCode) || !_date.isValid() || !setDate(_date)) { + return {}; + } + + return date(); +} + +CalendarEvents::CalendarEventsPlugin::SubLabel HebrewCalendarProviderPrivate::subLabel(const QDate &date) +{ + auto sublabel = CalendarEvents::CalendarEventsPlugin::SubLabel{}; + + if (U_FAILURE(m_errorCode) || !date.isValid() || !setDate(date)) { + return sublabel; + } + + const bool isLocaleHebrew = QLocale::system().language() == QLocale::Hebrew; + + sublabel.dayLabel = isLocaleHebrew ? formattedDateString("d") : QString::number(day()); + const QString hebrewDateString = formattedDateString("d בMMMM y"); // See https://unicode-org.github.io/cldr/ldml/tr35-dates.html + // Translated month names are available in https://github.com/unicode-org/icu/tree/main/icu4c/source/data/locales + sublabel.label = isLocaleHebrew ? hebrewDateString + : i18ndc("plasma_calendar_alternatecalendar", + "%1 Day number %2 Translated month name in Hebrew/Jewish calendar %3 Year number %4 Full date in Hebrew", + "%1 %2, %3 (%4)", + QString::number(day()), + formattedDateStringInNativeLanguage("MMMM"), + QString::number(year()), + hebrewDateString); + sublabel.priority = CalendarEvents::CalendarEventsPlugin::SubLabelPriority::Low; + + return sublabel; +} + +HebrewCalendarProvider::HebrewCalendarProvider(QObject *parent, CalendarSystem::System calendarSystem, const QDate &startDate, const QDate &endDate) + : AbstractCalendarProvider(parent, calendarSystem, startDate, endDate) + , d(std::make_unique()) +{ + Q_ASSERT(m_calendarSystem == CalendarSystem::Hebrew); +} + +HebrewCalendarProvider::~HebrewCalendarProvider() +{ +} + +QCalendar::YearMonthDay HebrewCalendarProvider::fromGregorian(const QDate &date) const +{ + return d->fromGregorian(date); +} + +CalendarEvents::CalendarEventsPlugin::SubLabel HebrewCalendarProvider::subLabel(const QDate &date) const +{ + return d->subLabel(date); +} diff --git a/plasmacalendarplugins/alternatecalendar/provider/hebrewcalendar.h b/plasmacalendarplugins/alternatecalendar/provider/hebrewcalendar.h new file mode 100644 index 00000000..1d4ca90b --- /dev/null +++ b/plasmacalendarplugins/alternatecalendar/provider/hebrewcalendar.h @@ -0,0 +1,27 @@ +/* + SPDX-FileCopyrightText: 2023 Fushan Wen + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include "abstractcalendarprovider.h" + +/** + * @brief An alternate calendar provider for Hebrew/Jewish calendar system. + */ +class HebrewCalendarProvider : public AbstractCalendarProvider +{ + Q_OBJECT + +public: + explicit HebrewCalendarProvider(QObject *parent, CalendarSystem::System calendarSystem, const QDate &startDate, const QDate &endDate); + ~HebrewCalendarProvider() override; + + QCalendar::YearMonthDay fromGregorian(const QDate &date) const override; + CalendarEvents::CalendarEventsPlugin::SubLabel subLabel(const QDate &date) const override; + +private: + const std::unique_ptr d; +}; diff --git a/plasmacalendarplugins/alternatecalendar/provider/icucalendar_p.cpp b/plasmacalendarplugins/alternatecalendar/provider/icucalendar_p.cpp new file mode 100644 index 00000000..06713275 --- /dev/null +++ b/plasmacalendarplugins/alternatecalendar/provider/icucalendar_p.cpp @@ -0,0 +1,82 @@ +/* + SPDX-FileCopyrightText: 2021 Gary Wang + SPDX-FileCopyrightText: 2022 Fushan Wen + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "icucalendar_p.h" + +#include +#include + +ICUCalendarPrivate::ICUCalendarPrivate() + : m_errorCode(U_ZERO_ERROR) + , m_GregorianCalendar(icu::Calendar::createInstance("en_US@calendar=gregorian", m_errorCode)) +{ +} + +ICUCalendarPrivate::~ICUCalendarPrivate() +{ +} + +int32_t ICUCalendarPrivate::year() const +{ + const int32_t year = m_calendar->get(UCAL_YEAR, m_errorCode); + + if (U_FAILURE(m_errorCode)) { + return -1; + } + + return year; +} + +int32_t ICUCalendarPrivate::month() const +{ + const int32_t month = m_calendar->get(UCAL_MONTH, m_errorCode); + + if (U_FAILURE(m_errorCode)) { + return -1; + } + + return month + 1; +} + +int32_t ICUCalendarPrivate::day() const +{ + const int32_t day = m_calendar->get(UCAL_DATE, m_errorCode); + + if (U_FAILURE(m_errorCode)) { + return -1; + } + + return day; +} + +QCalendar::YearMonthDay ICUCalendarPrivate::date() const +{ + return QCalendar::YearMonthDay(year(), month(), day()); +} + +bool ICUCalendarPrivate::setDate(const QDate &date) +{ + // icu: Month value is 0-based. e.g., 0 for January. + m_GregorianCalendar->set(date.year(), date.month() - 1, date.day()); + + const UDate time = m_GregorianCalendar->getTime(m_errorCode); + + if (U_FAILURE(m_errorCode)) { + return false; + } + + m_calendar->setTime(time, m_errorCode); + + return !U_FAILURE(m_errorCode); +} + +bool ICUCalendarPrivate::setTime(double time) +{ + m_calendar->setTime(time, m_errorCode); + + return !U_FAILURE(m_errorCode); +} diff --git a/plasmacalendarplugins/alternatecalendar/provider/icucalendar_p.h b/plasmacalendarplugins/alternatecalendar/provider/icucalendar_p.h new file mode 100644 index 00000000..580c38fb --- /dev/null +++ b/plasmacalendarplugins/alternatecalendar/provider/icucalendar_p.h @@ -0,0 +1,72 @@ +/* + SPDX-FileCopyrightText: 2021 Gary Wang + SPDX-FileCopyrightText: 2022 Fushan Wen + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include +#include + +#include + +class QString; + +class ICUCalendarPrivate +{ +public: + /** + * Initialize the Gregorian Calendar, which will be used in date conversion. + */ + explicit ICUCalendarPrivate(); + virtual ~ICUCalendarPrivate(); + + /** + * Returns the value for a given time field in the alternate calendar. + */ + int32_t year() const; + int32_t month() const; + int32_t day() const; + + /** + * Returns the date from the alternate calendar. + * + * @return the date from the alternate calendar + */ + QCalendar::YearMonthDay date() const; + + /** + * Sets the date in the Gregorian Calendar, and convert the date to + * the alternate calendar. + * + * @param date the Gregorian Calendar's date to be converted + * @return @c true if the date is successfully set, @c false otherwise + */ + bool setDate(const QDate &date); + + /** + * Sets the alternate calendar's current time with the given time. + * + * @param time the Gregorian Calendar's time as milliseconds + * @return @c true if the time is successfully set, @c false otherwise + */ + bool setTime(double time); + +protected: + /** + * Alternate calendar + */ + std::unique_ptr m_calendar; + /** + * Standard ICU4C error code. + */ + mutable UErrorCode m_errorCode; + +private: + /** + * Gregorian Calendar + */ + const std::unique_ptr m_GregorianCalendar; +}; diff --git a/plasmacalendarplugins/alternatecalendar/provider/indiancalendar.cpp b/plasmacalendarplugins/alternatecalendar/provider/indiancalendar.cpp new file mode 100644 index 00000000..e81ddd08 --- /dev/null +++ b/plasmacalendarplugins/alternatecalendar/provider/indiancalendar.cpp @@ -0,0 +1,98 @@ +/* + SPDX-FileCopyrightText: 2022 Fushan Wen + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "indiancalendar.h" + +class IndianCalendarProviderPrivate : public ICUCalendarPrivate +{ +public: + explicit IndianCalendarProviderPrivate(); + + /** + * For formatting, see the documentation of SimpleDateFormat: + * https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classicu_1_1SimpleDateFormat.html#details + */ + QString formattedDateStringInNativeLanguage(const icu::UnicodeString &str) const; + + QCalendar::YearMonthDay fromGregorian(const QDate &_date); + CalendarEvents::CalendarEventsPlugin::SubLabel subLabel(const QDate &date); + +private: + // Translated month names are available in https://github.com/unicode-org/icu/tree/main/icu4c/source/data/locales + icu::Locale m_nativeLocale; +}; + +IndianCalendarProviderPrivate::IndianCalendarProviderPrivate() + : ICUCalendarPrivate() + , m_nativeLocale(icu::Locale(QLocale::system().name().toLatin1(), 0, 0, "calendar=indian;")) +{ + if (U_FAILURE(m_errorCode)) { + return; // Failed to create m_GregorianCalendar + } + + m_calendar.reset(icu::Calendar::createInstance("en_US@calendar=indian", m_errorCode)); +} + +QString IndianCalendarProviderPrivate::formattedDateStringInNativeLanguage(const icu::UnicodeString &str) const +{ + UErrorCode errorCode = U_ZERO_ERROR; + icu::UnicodeString dateString; + icu::SimpleDateFormat formatter(str, m_nativeLocale, errorCode); + formatter.setCalendar(*m_calendar); + formatter.format(m_calendar->getTime(errorCode), dateString); + + return QStringView(dateString.getBuffer(), dateString.length()).toString(); +} + +QCalendar::YearMonthDay IndianCalendarProviderPrivate::fromGregorian(const QDate &_date) +{ + if (U_FAILURE(m_errorCode) || !_date.isValid() || !setDate(_date)) { + return {}; + } + + return date(); +} + +CalendarEvents::CalendarEventsPlugin::SubLabel IndianCalendarProviderPrivate::subLabel(const QDate &date) +{ + auto sublabel = CalendarEvents::CalendarEventsPlugin::SubLabel{}; + + if (U_FAILURE(m_errorCode) || !date.isValid() || !setDate(date)) { + return sublabel; + } + + sublabel.dayLabel = QString::number(day()); + sublabel.label = i18ndc("plasma_calendar_alternatecalendar", + "@label %1 day %2 month name in India National Calendar %3 year", + "%1 %2, %3", + sublabel.dayLabel, + formattedDateStringInNativeLanguage("MMMM"), + QString::number(year())); + sublabel.priority = CalendarEvents::CalendarEventsPlugin::SubLabelPriority::Low; + + return sublabel; +} + +IndianCalendarProvider::IndianCalendarProvider(QObject *parent, CalendarSystem::System calendarSystem, const QDate &startDate, const QDate &endDate) + : AbstractCalendarProvider(parent, calendarSystem, startDate, endDate) + , d(std::make_unique()) +{ + Q_ASSERT(m_calendarSystem == CalendarSystem::Indian); +} + +IndianCalendarProvider::~IndianCalendarProvider() +{ +} + +QCalendar::YearMonthDay IndianCalendarProvider::fromGregorian(const QDate &date) const +{ + return d->fromGregorian(date); +} + +CalendarEvents::CalendarEventsPlugin::SubLabel IndianCalendarProvider::subLabel(const QDate &date) const +{ + return d->subLabel(date); +} diff --git a/plasmacalendarplugins/alternatecalendar/provider/indiancalendar.h b/plasmacalendarplugins/alternatecalendar/provider/indiancalendar.h new file mode 100644 index 00000000..7bca027c --- /dev/null +++ b/plasmacalendarplugins/alternatecalendar/provider/indiancalendar.h @@ -0,0 +1,30 @@ +/* + SPDX-FileCopyrightText: 2022 Fushan Wen + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include "abstractcalendarprovider.h" +#include "icucalendar_p.h" + +/** + * @short An alternate calendar provider for Indian calendar system. + * + * This class presents an alternate calendar provider for Indian calendar system. + */ +class IndianCalendarProvider : public AbstractCalendarProvider +{ + Q_OBJECT + +public: + explicit IndianCalendarProvider(QObject *parent, CalendarSystem::System calendarSystem, const QDate &startDate, const QDate &endDate); + ~IndianCalendarProvider() override; + + QCalendar::YearMonthDay fromGregorian(const QDate &date) const override; + CalendarEvents::CalendarEventsPlugin::SubLabel subLabel(const QDate &date) const override; + +private: + const std::unique_ptr d; +}; diff --git a/plasmacalendarplugins/alternatecalendar/provider/islamiccalendar.cpp b/plasmacalendarplugins/alternatecalendar/provider/islamiccalendar.cpp new file mode 100644 index 00000000..e6a4547d --- /dev/null +++ b/plasmacalendarplugins/alternatecalendar/provider/islamiccalendar.cpp @@ -0,0 +1,164 @@ +/* + SPDX-FileCopyrightText: 2023 Fushan Wen + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "islamiccalendar.h" + +#include "icucalendar_p.h" +#include + +class IslamicCalendarProviderPrivate : public ICUCalendarPrivate +{ +public: + explicit IslamicCalendarProviderPrivate(CalendarSystem::System calendarSystem); + + /** + * For formatting, see the documentation of SimpleDateFormat: + * https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classicu_1_1SimpleDateFormat.html#details + */ + QString formattedDateString(const icu::UnicodeString &str) const; + QString formattedDateStringInNativeLanguage(const icu::UnicodeString &str) const; + + QCalendar::YearMonthDay fromGregorian(const QDate &_date); + CalendarEvents::CalendarEventsPlugin::SubLabel subLabel(const QDate &date); + +private: + // See https://unicode-org.github.io/icu/userguide/locale/#keywords for available keywords + icu::Locale m_arabicLocale; + icu::Locale m_nativeLocale; + bool m_isArabicNumberingSystem = false; +}; + +IslamicCalendarProviderPrivate::IslamicCalendarProviderPrivate(CalendarSystem::System calendarSystem) + : ICUCalendarPrivate() +{ + if (U_FAILURE(m_errorCode)) { + return; // Failed to create m_GregorianCalendar + } + + // See https://github.com/unicode-org/cldr/blob/main/common/bcp47/number.xml for available number systems + // See https://cldr.unicode.org/development/development-process/design-proposals/islamic-calendar-types for available Islamic caneldar types + const QByteArray iso639Language = QLocale::languageToCode(QLocale::system().language()).toLatin1().toLower(); + const QByteArray iso3166Country = QLocale::territoryToCode(QLocale::system().territory()).toLatin1().toUpper(); + const char *keywords = nullptr; + QLocale::Language typicalLanguage = QLocale::Arabic; + QLocale::Territory typicalTerritory = QLocale::SaudiArabia; + switch (calendarSystem) { + case CalendarSystem::Jalali: { + keywords = "calendar=persian;"; + typicalLanguage = QLocale::Persian; + typicalTerritory = QLocale::Iran; + break; + } + case CalendarSystem::Islamic: { + keywords = "calendar=islamic;"; + break; + } + case CalendarSystem::IslamicCivil: { + keywords = "calendar=islamic-civil;"; + break; + } + case CalendarSystem::IslamicUmalqura: { + keywords = "calendar=islamic-umalqura;"; + break; + } + default: + Q_UNREACHABLE(); + } + + m_nativeLocale = icu::Locale(iso639Language, iso3166Country, nullptr, keywords); + std::unique_ptr numberingSystem(icu::NumberingSystem::createInstance(m_nativeLocale, m_errorCode)); + m_isArabicNumberingSystem = U_SUCCESS(m_errorCode) && QByteArrayView(numberingSystem->getName()).startsWith(QByteArrayView("arab")); + if (!m_isArabicNumberingSystem) { + m_arabicLocale = icu::Locale(QLocale::languageToCode(typicalLanguage).toLatin1().toLower(), + QLocale::territoryToCode(typicalTerritory).toLatin1().toUpper(), + nullptr, + keywords); + } + + m_calendar.reset(icu::Calendar::createInstance(m_nativeLocale, m_errorCode)); +} + +QString IslamicCalendarProviderPrivate::formattedDateString(const icu::UnicodeString &str) const +{ + Q_ASSERT(!m_isArabicNumberingSystem); + UErrorCode errorCode = U_ZERO_ERROR; + icu::UnicodeString dateString; + icu::SimpleDateFormat formatter(str, m_arabicLocale, errorCode); + formatter.setCalendar(*m_calendar); + formatter.format(m_calendar->getTime(errorCode), dateString); + + return QStringView(dateString.getBuffer(), dateString.length()).toString(); +} + +QString IslamicCalendarProviderPrivate::formattedDateStringInNativeLanguage(const icu::UnicodeString &str) const +{ + UErrorCode errorCode = U_ZERO_ERROR; + icu::UnicodeString dateString; + icu::SimpleDateFormat formatter(str, m_nativeLocale, errorCode); + formatter.setCalendar(*m_calendar); + formatter.format(m_calendar->getTime(errorCode), dateString); + + return QStringView(dateString.getBuffer(), dateString.length()).toString(); +} + +QCalendar::YearMonthDay IslamicCalendarProviderPrivate::fromGregorian(const QDate &_date) +{ + if (U_FAILURE(m_errorCode) || !_date.isValid() || !setDate(_date)) { + return {}; + } + + return date(); +} + +CalendarEvents::CalendarEventsPlugin::SubLabel IslamicCalendarProviderPrivate::subLabel(const QDate &date) +{ + auto sublabel = CalendarEvents::CalendarEventsPlugin::SubLabel{}; + + if (U_FAILURE(m_errorCode) || !date.isValid() || !setDate(date)) { + return sublabel; + } + + sublabel.dayLabel = formattedDateStringInNativeLanguage("d"); + // Translated month names are available in https://github.com/unicode-org/icu/tree/main/icu4c/source/data/locales + sublabel.label = m_isArabicNumberingSystem + ? formattedDateStringInNativeLanguage("d MMMM yyyy") + : i18ndc("plasma_calendar_alternatecalendar", + "@label %1 Day number %2 Month name in Islamic Calendar %3 Year number %4 Islamic calendar date in Arabic", + "%1 %2, %3 (%4)", + QString::number(day()), + formattedDateStringInNativeLanguage("MMMM"), + QString::number(year()), + formattedDateString("d MMMM yyyy")); + sublabel.priority = CalendarEvents::CalendarEventsPlugin::SubLabelPriority::Low; + + return sublabel; +} + +IslamicCalendarProvider::IslamicCalendarProvider(QObject *parent, + CalendarSystem::System calendarSystem, + const QDate &startDate, + const QDate &endDate, + int dateOffset) + : AbstractCalendarProvider(parent, calendarSystem, startDate, endDate, dateOffset) + , d(std::make_unique(calendarSystem)) +{ + Q_ASSERT(m_calendarSystem == CalendarSystem::Jalali || m_calendarSystem == CalendarSystem::Islamic || m_calendarSystem == CalendarSystem::IslamicCivil + || m_calendarSystem == CalendarSystem::IslamicUmalqura); +} + +IslamicCalendarProvider::~IslamicCalendarProvider() +{ +} + +QCalendar::YearMonthDay IslamicCalendarProvider::fromGregorian(const QDate &date) const +{ + return d->fromGregorian(date); +} + +CalendarEvents::CalendarEventsPlugin::SubLabel IslamicCalendarProvider::subLabel(const QDate &date) const +{ + return d->subLabel(date); +} diff --git a/plasmacalendarplugins/alternatecalendar/provider/islamiccalendar.h b/plasmacalendarplugins/alternatecalendar/provider/islamiccalendar.h new file mode 100644 index 00000000..fecfa33f --- /dev/null +++ b/plasmacalendarplugins/alternatecalendar/provider/islamiccalendar.h @@ -0,0 +1,31 @@ +/* + SPDX-FileCopyrightText: 2023 Fushan Wen + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include "abstractcalendarprovider.h" + +/** + * @brief An alternate calendar provider for Islamic/the Hijri calendar system. + * + * There are 3 kinds of Islamic calendars. + * See https://cldr.unicode.org/development/development-process/design-proposals/islamic-calendar-types + * for more details. + */ +class IslamicCalendarProvider : public AbstractCalendarProvider +{ + Q_OBJECT + +public: + explicit IslamicCalendarProvider(QObject *parent, CalendarSystem::System calendarSystem, const QDate &startDate, const QDate &endDate, int dateOffset); + ~IslamicCalendarProvider() override; + + QCalendar::YearMonthDay fromGregorian(const QDate &date) const override; + CalendarEvents::CalendarEventsPlugin::SubLabel subLabel(const QDate &date) const override; + +private: + const std::unique_ptr d; +}; diff --git a/plasmacalendarplugins/alternatecalendar/provider/qtcalendar.cpp b/plasmacalendarplugins/alternatecalendar/provider/qtcalendar.cpp new file mode 100644 index 00000000..4038032c --- /dev/null +++ b/plasmacalendarplugins/alternatecalendar/provider/qtcalendar.cpp @@ -0,0 +1,61 @@ +/* + SPDX-FileCopyrightText: 2022 Fushan Wen + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "qtcalendar.h" + +namespace +{ +constexpr QCalendar::System toQCalendarSystem(CalendarSystem::System calendarSystem) +{ + switch (calendarSystem) { + case CalendarSystem::System::Jalali: + return QCalendar::System::Jalali; + case CalendarSystem::System::IslamicCivil: + return QCalendar::System::IslamicCivil; + default: + return static_cast(calendarSystem); + } +} +} + +QtCalendarProvider::QtCalendarProvider(QObject *parent, CalendarSystem::System calendarSystem, const QDate &startDate, const QDate &endDate, int dateOffset) + : AbstractCalendarProvider(parent, calendarSystem, startDate, endDate, dateOffset) + , m_calendar(QCalendar(toQCalendarSystem(calendarSystem))) +{ + Q_ASSERT(toQCalendarSystem(calendarSystem) <= QCalendar::System::Last); +} + +QtCalendarProvider::~QtCalendarProvider() +{ +} + +QCalendar::YearMonthDay QtCalendarProvider::fromGregorian(const QDate &date) const +{ + if (!date.isValid()) { + return {}; + } + + return m_calendar.partsFromDate(date); +} + +CalendarEvents::CalendarEventsPlugin::SubLabel QtCalendarProvider::subLabel(const QDate &date) const +{ + auto sublabel = CalendarEvents::CalendarEventsPlugin::SubLabel{}; + + if (!date.isValid()) { + return sublabel; + } + + const QCalendar::YearMonthDay altDate = fromGregorian(date); + sublabel.label = i18ndc("plasma_calendar_alternatecalendar", + "@label %1 day %2 month name %3 year", + "%1 %2, %3", + QString::number(altDate.day), + m_calendar.standaloneMonthName(QLocale::system(), altDate.month, altDate.year), + QString::number(altDate.year)); + + return sublabel; +} diff --git a/plasmacalendarplugins/alternatecalendar/provider/qtcalendar.h b/plasmacalendarplugins/alternatecalendar/provider/qtcalendar.h new file mode 100644 index 00000000..da74596d --- /dev/null +++ b/plasmacalendarplugins/alternatecalendar/provider/qtcalendar.h @@ -0,0 +1,31 @@ +/* + SPDX-FileCopyrightText: 2022 Fushan Wen + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include "abstractcalendarprovider.h" + +/** + * @short An alternate calendar provider for the calendar system available in QCalendar. + * + * This class presents an alternate calendar provider for the calendar system available + * in QCalendar. + * The available calendar systems are listed in QCalendar::availableCalendars(). + */ +class QtCalendarProvider : public AbstractCalendarProvider +{ + Q_OBJECT + +public: + explicit QtCalendarProvider(QObject *parent, CalendarSystem::System calendarSystem, const QDate &startDate, const QDate &endDate, int dateOffset = 0); + ~QtCalendarProvider() override; + + QCalendar::YearMonthDay fromGregorian(const QDate &date) const override; + CalendarEvents::CalendarEventsPlugin::SubLabel subLabel(const QDate &date) const override; + +private: + const QCalendar m_calendar; +}; diff --git a/plasmacalendarplugins/alternatecalendar/provider/solarutils.h b/plasmacalendarplugins/alternatecalendar/provider/solarutils.h new file mode 100644 index 00000000..4f19dea1 --- /dev/null +++ b/plasmacalendarplugins/alternatecalendar/provider/solarutils.h @@ -0,0 +1,3079 @@ +/* + SPDX-FileCopyrightText: 2019 - 2022 UnionTech Software Technology Co., Ltd. + + SPDX-License-Identifier: LGPL-3.0-or-later +*/ + +#pragma once + +#include +#include +#include +#include + +namespace SolarUtils +{ +constexpr double J2000 = 2451545.0; +struct MoonEclipticParameter { + double Lp; + double D; + double M; + double Mp; + double F; + double E; +}; + +struct MoonEclipticLongitudeCoeff { + double D; + double M; + double Mp; + double F; + double EiA; + double ErA; +}; + +struct EarthNutationParameter { + double D; + double M; + double Mp; + double F; + double Omega; +}; + +struct NuationCoefficient { + double D; + double M; + double Mp; + double F; + double Omega; + double Sine1; + double Sine2; + double Cosine1; + double Cosine2; +}; + +constexpr std::array s_moonLongitude{ + MoonEclipticLongitudeCoeff{0, 0, 1, 0, 6288744, -20905355}, + {2, 0, -1, 0, 1274027, -3699111}, + {2, 0, 0, 0, 658314, -2955968}, + {0, 0, 2, 0, 213618, -569925}, + {0, 1, 0, 0, -185116, 48888}, + {0, 0, 0, 2, -114332, -3149}, + {2, 0, -2, 0, 58793, 246158}, + {2, -1, -1, 0, 57066, -152138}, + {2, 0, 1, 0, 53322, -170733}, + {2, -1, 0, 0, 45758, -204586}, + {0, 1, -1, 0, -40923, -129620}, + {1, 0, 0, 0, -34720, 108743}, + {0, 1, 1, 0, -30383, 104755}, + {2, 0, 0, -2, 15327, 10321}, + {0, 0, 1, 2, -12528, 0}, + {0, 0, 1, -2, 10980, 79661}, + {4, 0, -1, 0, 10675, -34782}, + {0, 0, 3, 0, 10034, -23210}, + {4, 0, -2, 0, 8548, -21636}, + {2, 1, -1, 0, -7888, 24208}, + {2, 1, 0, 0, -6766, 30824}, + {1, 0, -1, 0, -5163, -8379}, + {1, 1, 0, 0, 4987, -16675}, + {2, -1, 1, 0, 4036, -12831}, + {2, 0, 2, 0, 3994, -10445}, + {4, 0, 0, 0, 3861, -11650}, + {2, 0, -3, 0, 3665, 14403}, + {0, 1, -2, 0, -2689, -7003}, + {2, 0, -1, 2, -2602, 0}, + {2, -1, -2, 0, 2390, 10056}, + {1, 0, 1, 0, -2348, 6322}, + {2, -2, 0, 0, 2236, -9884}, + {0, 1, 2, 0, -2120, 5751}, + {0, 2, 0, 0, -2069, 0}, + {2, -2, -1, 0, 2048, -4950}, + {2, 0, 1, -2, -1773, 4130}, + {2, 0, 0, 2, -1595, 0}, + {4, -1, -1, 0, 1215, -3958}, + {0, 0, 2, 2, -1110, 0}, + {3, 0, -1, 0, -892, 3258}, + {2, 1, 1, 0, -810, 2616}, + {4, -1, -2, 0, 759, -1897}, + {0, 2, -1, 0, -713, -2117}, + {2, 2, -1, 0, -700, 2354}, + {2, 1, -2, 0, 691, 0}, + {2, -1, 0, -2, 596, 0}, + {4, 0, 1, 0, 549, -1423}, + {0, 0, 4, 0, 537, -1117}, + {4, -1, 0, 0, 520, -1571}, + {1, 0, -2, 0, -487, -1739}, + {2, 1, 0, -2, -399, 0}, + {0, 0, 2, -2, -381, -4421}, + {1, 1, 1, 0, 351, 0}, + {3, 0, -2, 0, -340, 0}, + {4, 0, -3, 0, 330, 0}, + {2, -1, 2, 0, 327, 0}, + {0, 2, 1, 0, -323, 1165}, + {1, 1, -1, 0, 299, 0}, + {2, 0, 3, 0, 294, 0}, + {2, 0, -1, -2, 0, 8752}, +}; + +constexpr std::array s_nuation{ + NuationCoefficient{0, 0, 0, 0, 1, -171996, -174.2, 92025, 8.9}, + NuationCoefficient{-2, 0, 0, 2, 2, -13187, -1.6, 5736, -3.1}, + NuationCoefficient{0, 0, 0, 2, 2, -2274, -0.2, 977, -0.5}, + NuationCoefficient{0, 0, 0, 0, 2, 2062, 0.2, -895, 0.5}, + NuationCoefficient{0, 1, 0, 0, 0, 1426, -3.4, 54, -0.1}, + NuationCoefficient{0, 0, 1, 0, 0, 712, 0.1, -7, 0}, + NuationCoefficient{-2, 1, 0, 2, 2, -517, 1.2, 224, -0.6}, + NuationCoefficient{0, 0, 0, 2, 1, -386, -0.4, 200, 0}, + NuationCoefficient{0, 0, 1, 2, 2, -301, 0, 129, -0.1}, + NuationCoefficient{-2, -1, 0, 2, 2, 217, -0.5, -95, 0.3}, + NuationCoefficient{-2, 0, 1, 0, 0, -158, 0, 0, 0}, + NuationCoefficient{-2, 0, 0, 2, 1, 129, 0.1, -70, 0}, + NuationCoefficient{0, 0, -1, 2, 2, 123, 0, -53, 0}, + NuationCoefficient{2, 0, 0, 0, 0, 63, 0, 0, 0}, + NuationCoefficient{0, 0, 1, 0, 1, 63, 0.1, -33, 0}, + NuationCoefficient{2, 0, -1, 2, 2, -59, 0, 26, 0}, + NuationCoefficient{0, 0, -1, 0, 1, -58, -0.1, 32, 0}, + NuationCoefficient{0, 0, 1, 2, 1, -51, 0, 27, 0}, + NuationCoefficient{-2, 0, 2, 0, 0, 48, 0, 0, 0}, + NuationCoefficient{0, 0, -2, 2, 1, 46, 0, -24, 0}, + NuationCoefficient{2, 0, 0, 2, 2, -38, 0, 16, 0}, + NuationCoefficient{0, 0, 2, 2, 2, -31, 0, 13, 0}, + NuationCoefficient{0, 0, 2, 0, 0, 29, 0, 0, 0}, + NuationCoefficient{-2, 0, 1, 2, 2, 29, 0, -12, 0}, + NuationCoefficient{0, 0, 0, 2, 0, 26, 0, 0, 0}, + NuationCoefficient{-2, 0, 0, 2, 0, -22, 0, 0, 0}, + NuationCoefficient{0, 0, -1, 2, 1, 21, 0, -10, 0}, + NuationCoefficient{0, 2, 0, 0, 0, 17, -0.1, 0, 0}, + NuationCoefficient{2, 0, -1, 0, 1, 16, 0, -8, 0}, + NuationCoefficient{-2, 2, 0, 2, 2, -16, 0.1, 7, 0}, + NuationCoefficient{0, 1, 0, 0, 1, -15, 0, 9, 0}, + NuationCoefficient{-2, 0, 1, 0, 1, -13, 0, 7, 0}, + NuationCoefficient{0, -1, 0, 0, 1, -12, 0, 6, 0}, + NuationCoefficient{0, 0, 2, -2, 0, 11, 0, 0, 0}, + NuationCoefficient{2, 0, -1, 2, 1, -10, 0, 5, 0}, + NuationCoefficient{2, 0, 1, 2, 2, -8, 0, 3, 0}, + NuationCoefficient{0, 1, 0, 2, 2, 7, 0, -3, 0}, + NuationCoefficient{-2, 1, 1, 0, 0, -7, 0, 0, 0}, + NuationCoefficient{0, -1, 0, 2, 2, -7, 0, 3, 0}, + NuationCoefficient{2, 0, 0, 2, 1, -7, 0, 3, 0}, + NuationCoefficient{2, 0, 1, 0, 0, 6, 0, 0, 0}, + NuationCoefficient{-2, 0, 2, 2, 2, 6, 0, -3, 0}, + NuationCoefficient{-2, 0, 1, 2, 1, 6, 0, -3, 0}, + NuationCoefficient{2, 0, -2, 0, 1, -6, 0, 3, 0}, + NuationCoefficient{2, 0, 0, 0, 1, -6, 0, 3, 0}, + NuationCoefficient{0, -1, 1, 0, 0, 5, 0, 0, 0}, + NuationCoefficient{-2, -1, 0, 2, 1, -5, 0, 3, 0}, + NuationCoefficient{-2, 0, 0, 0, 1, -5, 0, 3, 0}, + NuationCoefficient{0, 0, 2, 2, 1, -5, 0, 3, 0}, + NuationCoefficient{-2, 0, 2, 0, 1, 4, 0, 0, 0}, + NuationCoefficient{-2, 1, 0, 2, 1, 4, 0, 0, 0}, + NuationCoefficient{0, 0, 1, -2, 0, 4, 0, 0, 0}, + NuationCoefficient{-1, 0, 1, 0, 0, -4, 0, 0, 0}, + NuationCoefficient{-2, 1, 0, 0, 0, -4, 0, 0, 0}, + NuationCoefficient{1, 0, 0, 0, 0, -4, 0, 0, 0}, + NuationCoefficient{0, 0, 1, 2, 0, 3, 0, 0, 0}, + NuationCoefficient{0, 0, -2, 2, 2, -3, 0, 0, 0}, + NuationCoefficient{-1, -1, 1, 0, 0, -3, 0, 0, 0}, + NuationCoefficient{0, 1, 1, 0, 0, -3, 0, 0, 0}, + NuationCoefficient{0, -1, 1, 2, 2, -3, 0, 0, 0}, + NuationCoefficient{2, -1, -1, 2, 2, -3, 0, 0, 0}, + NuationCoefficient{0, 0, 3, 2, 2, -3, 0, 0, 0}, + NuationCoefficient{2, -1, 0, 2, 2, -3, 0, 0, 0}, +}; + +constexpr double getEarthL0(double t) +{ + double result = 0.0; + result += 1.75347045673; + result += 0.03341656456 * std::cos(4.66925680417 + 6283.0758499914 * t); + result += 0.00034894275 * std::cos(4.62610241759 + 12566.1516999828 * t); + result += 3.417571e-05 * std::cos(2.82886579606 + 3.523118349 * t); + result += 3.497056e-05 * std::cos(2.74411800971 + 5753.3848848968 * t); + result += 3.135896e-05 * std::cos(3.62767041758 + 77713.7714681205 * t); + result += 2.676218e-05 * std::cos(4.41808351397 + 7860.4193924392 * t); + result += 2.342687e-05 * std::cos(6.13516237631 + 3930.2096962196 * t); + result += 1.273166e-05 * std::cos(2.03709655772 + 529.6909650946 * t); + result += 1.324292e-05 * std::cos(0.74246356352 + 11506.7697697936 * t); + result += 9.01855e-06 * std::cos(2.04505443513 + 26.2983197998 * t); + result += 1.199167e-05 * std::cos(1.10962944315 + 1577.3435424478 * t); + result += 8.57223e-06 * std::cos(3.50849156957 + 398.1490034082 * t); + result += 7.79786e-06 * std::cos(1.17882652114 + 5223.6939198022 * t); + result += 9.9025e-06 * std::cos(5.23268129594 + 5884.9268465832 * t); + result += 7.53141e-06 * std::cos(2.53339053818 + 5507.5532386674 * t); + result += 5.05264e-06 * std::cos(4.58292563052 + 18849.2275499742 * t); + result += 4.92379e-06 * std::cos(4.20506639861 + 775.522611324 * t); + result += 3.56655e-06 * std::cos(2.91954116867 + 0.0673103028 * t); + result += 2.84125e-06 * std::cos(1.89869034186 + 796.2980068164 * t); + result += 2.4281e-06 * std::cos(0.34481140906 + 5486.777843175 * t); + result += 3.17087e-06 * std::cos(5.84901952218 + 11790.6290886588 * t); + result += 2.71039e-06 * std::cos(0.31488607649 + 10977.078804699 * t); + result += 2.0616e-06 * std::cos(4.80646606059 + 2544.3144198834 * t); + result += 2.05385e-06 * std::cos(1.86947813692 + 5573.1428014331 * t); + result += 2.02261e-06 * std::cos(2.45767795458 + 6069.7767545534 * t); + result += 1.26184e-06 * std::cos(1.0830263021 + 20.7753954924 * t); + result += 1.55516e-06 * std::cos(0.83306073807 + 213.299095438 * t); + result += 1.15132e-06 * std::cos(0.64544911683 + 0.9803210682 * t); + result += 1.02851e-06 * std::cos(0.63599846727 + 4694.0029547076 * t); + result += 1.01724e-06 * std::cos(4.26679821365 + 7.1135470008 * t); + result += 9.9206e-07 * std::cos(6.20992940258 + 2146.1654164752 * t); + result += 1.32212e-06 * std::cos(3.41118275555 + 2942.4634232916 * t); + result += 9.7607e-07 * std::cos(0.6810127227 + 155.4203994342 * t); + result += 8.5128e-07 * std::cos(1.29870743025 + 6275.9623029906 * t); + result += 7.4651e-07 * std::cos(1.75508916159 + 5088.6288397668 * t); + result += 1.01895e-06 * std::cos(0.97569221824 + 15720.8387848784 * t); + result += 8.4711e-07 * std::cos(3.67080093025 + 71430.6956181291 * t); + result += 7.3547e-07 * std::cos(4.67926565481 + 801.8209311238 * t); + result += 7.3874e-07 * std::cos(3.50319443167 + 3154.6870848956 * t); + result += 7.8756e-07 * std::cos(3.03698313141 + 12036.4607348882 * t); + result += 7.9637e-07 * std::cos(1.807913307 + 17260.1546546904 * t); + result += 8.5803e-07 * std::cos(5.98322631256 + 161000.685737674 * t); + result += 5.6963e-07 * std::cos(2.78430398043 + 6286.5989683404 * t); + result += 6.1148e-07 * std::cos(1.81839811024 + 7084.8967811152 * t); + result += 6.9627e-07 * std::cos(0.83297596966 + 9437.762934887 * t); + result += 5.6116e-07 * std::cos(4.38694880779 + 14143.4952424306 * t); + result += 6.2449e-07 * std::cos(3.97763880587 + 8827.3902698748 * t); + result += 5.1145e-07 * std::cos(0.28306864501 + 5856.4776591154 * t); + result += 5.5577e-07 * std::cos(3.47006009062 + 6279.5527316424 * t); + result += 4.1036e-07 * std::cos(5.36817351402 + 8429.2412664666 * t); + result += 5.1605e-07 * std::cos(1.33282746983 + 1748.016413067 * t); + result += 5.1992e-07 * std::cos(0.18914945834 + 12139.5535091068 * t); + result += 4.9e-07 * std::cos(0.48735065033 + 1194.4470102246 * t); + result += 3.92e-07 * std::cos(6.16832995016 + 10447.3878396044 * t); + result += 3.5566e-07 * std::cos(1.77597314691 + 6812.766815086 * t); + result += 3.677e-07 * std::cos(6.04133859347 + 10213.285546211 * t); + result += 3.6596e-07 * std::cos(2.56955238628 + 1059.3819301892 * t); + result += 3.3291e-07 * std::cos(0.59309499459 + 17789.845619785 * t); + result += 3.5954e-07 * std::cos(1.70876111898 + 2352.8661537718 * t); + result += 4.0938e-07 * std::cos(2.39850881707 + 19651.048481098 * t); + result += 3.0047e-07 * std::cos(2.73975123935 + 1349.8674096588 * t); + result += 3.0412e-07 * std::cos(0.44294464135 + 83996.8473181119 * t); + result += 2.3663e-07 * std::cos(0.48473567763 + 8031.0922630584 * t); + result += 2.3574e-07 * std::cos(2.06527720049 + 3340.6124266998 * t); + result += 2.1089e-07 * std::cos(4.14825464101 + 951.7184062506 * t); + result += 2.4738e-07 * std::cos(0.21484762138 + 3.5904286518 * t); + result += 2.5352e-07 * std::cos(3.16470953405 + 4690.4798363586 * t); + result += 2.282e-07 * std::cos(5.22197888032 + 4705.7323075436 * t); + result += 2.1419e-07 * std::cos(1.42563735525 + 16730.4636895958 * t); + result += 2.1891e-07 * std::cos(5.55594302562 + 553.5694028424 * t); + result += 1.7481e-07 * std::cos(4.56052900359 + 135.0650800354 * t); + result += 1.9925e-07 * std::cos(5.22208471269 + 12168.0026965746 * t); + result += 1.986e-07 * std::cos(5.77470167653 + 6309.3741697912 * t); + result += 2.03e-07 * std::cos(0.37133792946 + 283.8593188652 * t); + result += 1.4421e-07 * std::cos(4.19315332546 + 242.728603974 * t); + result += 1.6225e-07 * std::cos(5.98837722564 + 11769.8536931664 * t); + result += 1.5077e-07 * std::cos(4.19567181073 + 6256.7775301916 * t); + result += 1.9124e-07 * std::cos(3.82219996949 + 23581.2581773176 * t); + result += 1.8888e-07 * std::cos(5.38626880969 + 149854.400134808 * t); + result += 1.4346e-07 * std::cos(3.72355084422 + 38.0276726358 * t); + result += 1.7898e-07 * std::cos(2.21490735647 + 13367.9726311066 * t); + result += 1.2054e-07 * std::cos(2.62229588349 + 955.5997416086 * t); + result += 1.1287e-07 * std::cos(0.17739328092 + 4164.311989613 * t); + result += 1.3971e-07 * std::cos(4.40138139996 + 6681.2248533996 * t); + result += 1.3621e-07 * std::cos(1.88934471407 + 7632.9432596502 * t); + result += 1.2503e-07 * std::cos(1.13052412208 + 5.5229243074 * t); + result += 1.0498e-07 * std::cos(5.35909518669 + 1592.5960136328 * t); + result += 9.803e-08 * std::cos(0.99947478995 + 11371.7046897582 * t); + result += 9.22e-08 * std::cos(4.57138609781 + 4292.3308329504 * t); + result += 1.0327e-07 * std::cos(6.19982566125 + 6438.4962494256 * t); + result += 1.2003e-07 * std::cos(1.003514567 + 632.7837393132 * t); + result += 1.0827e-07 * std::cos(0.32734520222 + 103.0927742186 * t); + result += 8.356e-08 * std::cos(4.53902685948 + 25132.3033999656 * t); + result += 1.0005e-07 * std::cos(6.0291496328 + 5746.271337896 * t); + result += 8.409e-08 * std::cos(3.29946744189 + 7234.794256242 * t); + result += 8.006e-08 * std::cos(5.82145271907 + 28.4491874678 * t); + result += 1.0523e-07 * std::cos(0.93871805506 + 11926.2544136688 * t); + result += 7.686e-08 * std::cos(3.12142363172 + 7238.6755916 * t); + result += 9.378e-08 * std::cos(2.62414241032 + 5760.4984318976 * t); + result += 8.127e-08 * std::cos(6.11228001785 + 4732.0306273434 * t); + result += 9.232e-08 * std::cos(0.48343968736 + 522.5774180938 * t); + result += 9.802e-08 * std::cos(5.24413991147 + 27511.4678735372 * t); + result += 7.871e-08 * std::cos(0.99590177926 + 5643.1785636774 * t); + result += 8.123e-08 * std::cos(6.2705301365 + 426.598190876 * t); + result += 9.048e-08 * std::cos(5.33686335897 + 6386.16862421 * t); + result += 8.62e-08 * std::cos(4.16538210888 + 7058.5984613154 * t); + result += 6.297e-08 * std::cos(4.71724819317 + 6836.6452528338 * t); + result += 7.575e-08 * std::cos(3.97382858911 + 11499.6562227928 * t); + result += 7.756e-08 * std::cos(2.95729056763 + 23013.5395395872 * t); + result += 7.314e-08 * std::cos(0.60652505806 + 11513.8833167944 * t); + result += 5.955e-08 * std::cos(2.87641047971 + 6283.14316029419 * t); + result += 6.534e-08 * std::cos(5.79072926033 + 18073.7049386502 * t); + result += 7.188e-08 * std::cos(3.99831508699 + 74.7815985673 * t); + result += 7.346e-08 * std::cos(4.38582365437 + 316.3918696566 * t); + result += 5.413e-08 * std::cos(5.39199024641 + 419.4846438752 * t); + result += 5.127e-08 * std::cos(2.36062848786 + 10973.55568635 * t); + result += 7.056e-08 * std::cos(0.32258441903 + 263.0839233728 * t); + result += 6.625e-08 * std::cos(3.66475158672 + 17298.1823273262 * t); + result += 6.762e-08 * std::cos(5.91132535899 + 90955.5516944961 * t); + result += 4.938e-08 * std::cos(5.73672165674 + 9917.6968745098 * t); + result += 5.547e-08 * std::cos(2.45152597661 + 12352.8526045448 * t); + result += 5.958e-08 * std::cos(3.32051344676 + 6283.0085396886 * t); + result += 4.471e-08 * std::cos(2.06385999536 + 7079.3738568078 * t); + result += 6.153e-08 * std::cos(1.45823331144 + 233141.314404362 * t); + result += 4.348e-08 * std::cos(4.4234217548 + 5216.5803728014 * t); + result += 6.123e-08 * std::cos(1.07494905258 + 19804.8272915828 * t); + result += 4.488e-08 * std::cos(3.6528503715 + 206.1855484372 * t); + result += 4.02e-08 * std::cos(0.83995823171 + 20.3553193988 * t); + result += 5.188e-08 * std::cos(4.06503864016 + 6208.2942514241 * t); + result += 5.307e-08 * std::cos(0.38217636096 + 31441.6775697568 * t); + result += 3.785e-08 * std::cos(2.34369213733 + 3.881335358 * t); + result += 4.497e-08 * std::cos(3.27230796845 + 11015.1064773348 * t); + result += 4.132e-08 * std::cos(0.92128915753 + 3738.761430108 * t); + result += 3.521e-08 * std::cos(5.97844807108 + 3894.1818295422 * t); + result += 4.215e-08 * std::cos(1.90601120623 + 245.8316462294 * t); + result += 3.701e-08 * std::cos(5.03069397926 + 536.8045120954 * t); + result += 3.865e-08 * std::cos(1.82634360607 + 11856.2186514245 * t); + result += 3.652e-08 * std::cos(1.01838584934 + 16200.7727245012 * t); + result += 3.39e-08 * std::cos(0.97785123922 + 8635.9420037632 * t); + result += 3.737e-08 * std::cos(2.95380107829 + 3128.3887650958 * t); + result += 3.507e-08 * std::cos(3.71291946325 + 6290.1893969922 * t); + result += 3.086e-08 * std::cos(3.64646921512 + 10.6366653498 * t); + result += 3.397e-08 * std::cos(1.10590684017 + 14712.317116458 * t); + result += 3.334e-08 * std::cos(0.83684924911 + 6496.3749454294 * t); + result += 2.805e-08 * std::cos(2.58504514144 + 14314.1681130498 * t); + result += 3.65e-08 * std::cos(1.08344142571 + 88860.0570709867 * t); + result += 3.388e-08 * std::cos(3.20185096055 + 5120.6011455836 * t); + result += 3.252e-08 * std::cos(3.47859752062 + 6133.5126528568 * t); + result += 2.553e-08 * std::cos(3.94869034189 + 1990.745017041 * t); + result += 3.52e-08 * std::cos(2.05559692878 + 244287.600007228 * t); + result += 2.565e-08 * std::cos(1.560717849 + 23543.2305046818 * t); + result += 2.621e-08 * std::cos(3.85639359951 + 266.6070417218 * t); + result += 2.955e-08 * std::cos(3.39692949667 + 9225.539273283 * t); + result += 2.876e-08 * std::cos(6.02635617464 + 154717.609887683 * t); + result += 2.395e-08 * std::cos(1.16131956403 + 10984.1923516998 * t); + result += 3.161e-08 * std::cos(1.32798718453 + 10873.9860304804 * t); + result += 3.163e-08 * std::cos(5.08946464629 + 21228.3920235458 * t); + result += 2.361e-08 * std::cos(4.27212906992 + 6040.3472460174 * t); + result += 3.03e-08 * std::cos(1.80209931347 + 35371.8872659764 * t); + result += 2.343e-08 * std::cos(3.576898605 + 10969.9652576982 * t); + result += 2.618e-08 * std::cos(2.57870156528 + 22483.8485744926 * t); + result += 2.113e-08 * std::cos(3.71393780256 + 65147.6197681377 * t); + result += 2.019e-08 * std::cos(0.81393923319 + 170.6728706192 * t); + result += 2.003e-08 * std::cos(0.38091017375 + 6172.869528772 * t); + result += 2.506e-08 * std::cos(3.74379142438 + 10575.4066829418 * t); + result += 2.381e-08 * std::cos(0.10581361289 + 7.046236698 * t); + result += 1.949e-08 * std::cos(4.86892513469 + 36.0278666774 * t); + result += 2.074e-08 * std::cos(4.2279477457 + 5650.2921106782 * t); + result += 1.924e-08 * std::cos(5.5946054986 + 6282.0955289232 * t); + result += 1.949e-08 * std::cos(1.07002512703 + 5230.807466803 * t); + result += 1.988e-08 * std::cos(5.19736046771 + 6262.300454499 * t); + result += 1.887e-08 * std::cos(3.74365662683 + 23.8784377478 * t); + result += 1.787e-08 * std::cos(1.25929682929 + 12559.038152982 * t); + result += 1.883e-08 * std::cos(1.90364058477 + 15.252471185 * t); + result += 1.816e-08 * std::cos(3.68083868442 + 15110.4661198662 * t); + result += 1.701e-08 * std::cos(4.4110589538 + 110.2063212194 * t); + result += 1.99e-08 * std::cos(3.93295788548 + 6206.8097787158 * t); + result += 2.103e-08 * std::cos(0.75354917468 + 13521.7514415914 * t); + result += 1.774e-08 * std::cos(0.48747535361 + 1551.045222648 * t); + result += 1.882e-08 * std::cos(0.86684493432 + 22003.9146348698 * t); + result += 1.924e-08 * std::cos(1.22898324132 + 709.9330485583 * t); + result += 2.009e-08 * std::cos(4.6285092198 + 6037.244203762 * t); + result += 1.924e-08 * std::cos(0.60231842508 + 6284.0561710596 * t); + result += 1.596e-08 * std::cos(3.98332956992 + 13916.0191096416 * t); + result += 1.664e-08 * std::cos(4.41939715469 + 8662.240323563 * t); + result += 1.971e-08 * std::cos(1.04560500503 + 18209.3302636602 * t); + result += 1.942e-08 * std::cos(4.31335979989 + 6244.9428143536 * t); + result += 1.476e-08 * std::cos(0.93271367331 + 2379.1644735716 * t); + result += 1.81e-08 * std::cos(0.49112137707 + 1.4844727083 * t); + result += 1.346e-08 * std::cos(1.51574702235 + 4136.9104335162 * t); + result += 1.528e-08 * std::cos(5.61835711404 + 6127.6554505572 * t); + result += 1.791e-08 * std::cos(3.22187270126 + 39302.096962196 * t); + result += 1.747e-08 * std::cos(3.05638656738 + 18319.5365848796 * t); + result += 1.431e-08 * std::cos(4.51153808594 + 20426.571092422 * t); + result += 1.695e-08 * std::cos(0.22047718414 + 25158.6017197654 * t); + result += 1.242e-08 * std::cos(4.46665769933 + 17256.6315363414 * t); + result += 1.463e-08 * std::cos(4.69242679213 + 14945.3161735544 * t); + result += 1.205e-08 * std::cos(1.86912144659 + 4590.910180489 * t); + result += 1.192e-08 * std::cos(2.74227166898 + 12569.6748183318 * t); + result += 1.222e-08 * std::cos(5.18120087482 + 5333.9002410216 * t); + result += 1.39e-08 * std::cos(5.42894648983 + 143571.324284816 * t); + result += 1.473e-08 * std::cos(1.70479245805 + 11712.9553182308 * t); + result += 1.362e-08 * std::cos(2.61069503292 + 6062.6632075526 * t); + result += 1.148e-08 * std::cos(6.0300180054 + 3634.6210245184 * t); + result += 1.198e-08 * std::cos(5.15294130422 + 10177.2576795336 * t); + result += 1.266e-08 * std::cos(0.11421493643 + 18422.6293590982 * t); + result += 1.411e-08 * std::cos(1.09908857534 + 3496.032826134 * t); + result += 1.349e-08 * std::cos(2.99805109633 + 17654.7805397496 * t); + result += 1.253e-08 * std::cos(2.79850152848 + 167283.761587666 * t); + result += 1.311e-08 * std::cos(1.60942984879 + 5481.2549188676 * t); + result += 1.079e-08 * std::cos(6.20304501787 + 3.2863574178 * t); + result += 1.181e-08 * std::cos(1.20653776978 + 131.5419616864 * t); + result += 1.254e-08 * std::cos(5.45103277798 + 6076.8903015542 * t); + result += 1.035e-08 * std::cos(2.32142722747 + 7342.4577801806 * t); + result += 1.117e-08 * std::cos(0.38838354256 + 949.1756089698 * t); + result += 9.66e-09 * std::cos(3.18341890851 + 11087.2851259184 * t); + result += 1.171e-08 * std::cos(3.39635049962 + 12562.6285816338 * t); + result += 1.121e-08 * std::cos(0.72627490378 + 220.4126424388 * t); + result += 1.024e-08 * std::cos(2.19378315386 + 11403.676995575 * t); + result += 8.88e-09 * std::cos(3.91173199285 + 4686.8894077068 * t); + result += 9.1e-09 * std::cos(1.98802695087 + 735.8765135318 * t); + result += 8.3e-09 * std::cos(0.48984915507 + 24072.9214697764 * t); + result += 1.096e-08 * std::cos(6.17377835617 + 5436.9930152402 * t); + result += 9.08e-09 * std::cos(0.44959639433 + 7477.522860216 * t); + result += 9.74e-09 * std::cos(1.52996238356 + 9623.6882766912 * t); + result += 8.4e-09 * std::cos(1.79543266333 + 5429.8794682394 * t); + result += 7.78e-09 * std::cos(6.17699177946 + 38.1330356378 * t); + result += 7.76e-09 * std::cos(4.09855402433 + 14.2270940016 * t); + result += 1.068e-08 * std::cos(4.64200173735 + 43232.3066584156 * t); + result += 9.54e-09 * std::cos(1.49988435748 + 1162.4747044078 * t); + result += 9.07e-09 * std::cos(0.86986870809 + 10344.2950653858 * t); + result += 9.31e-09 * std::cos(4.06044689031 + 28766.924424484 * t); + result += 7.39e-09 * std::cos(5.04368197372 + 639.897286314 * t); + result += 9.37e-09 * std::cos(3.4688469896 + 1589.0728952838 * t); + result += 7.63e-09 * std::cos(5.86304932998 + 16858.4825329332 * t); + result += 9.53e-09 * std::cos(4.20801492835 + 11190.377900137 * t); + result += 7.08e-09 * std::cos(1.7289998894 + 13095.8426650774 * t); + result += 9.69e-09 * std::cos(1.64439522215 + 29088.811415985 * t); + result += 7.17e-09 * std::cos(0.16688678895 + 11.729352836 * t); + result += 9.62e-09 * std::cos(3.53092337542 + 12416.5885028482 * t); + result += 7.47e-09 * std::cos(5.77866940346 + 12592.4500197826 * t); + result += 6.72e-09 * std::cos(1.91095796194 + 3.9321532631 * t); + result += 6.71e-09 * std::cos(5.46240843677 + 18052.9295431578 * t); + result += 6.75e-09 * std::cos(6.28311558823 + 4535.0594369244 * t); + result += 6.84e-09 * std::cos(0.3997501208 + 5849.3641121146 * t); + result += 7.99e-09 * std::cos(0.29851185294 + 12132.439962106 * t); + result += 7.58e-09 * std::cos(0.96370823331 + 1052.2683831884 * t); + result += 7.82e-09 * std::cos(5.33878339919 + 13517.8701062334 * t); + result += 7.3e-09 * std::cos(1.70106160291 + 17267.2682016912 * t); + result += 7.49e-09 * std::cos(2.59599901875 + 11609.8625440122 * t); + result += 7.34e-09 * std::cos(2.78417782952 + 640.8776073822 * t); + result += 6.88e-09 * std::cos(5.15048287468 + 16496.3613962024 * t); + result += 7.7e-09 * std::cos(1.62469589333 + 4701.1165017084 * t); + result += 6.33e-09 * std::cos(2.20587893893 + 25934.1243310894 * t); + result += 7.6e-09 * std::cos(4.21317219403 + 377.3736079158 * t); + result += 5.84e-09 * std::cos(2.13420121623 + 10557.5941608238 * t); + result += 5.74e-09 * std::cos(0.24250054587 + 9779.1086761254 * t); + result += 5.73e-09 * std::cos(3.16435264609 + 533.2140834436 * t); + result += 6.85e-09 * std::cos(3.19344289472 + 12146.6670561076 * t); + result += 6.75e-09 * std::cos(0.96179233959 + 10454.5013866052 * t); + result += 6.48e-09 * std::cos(1.46327342555 + 6268.8487559898 * t); + result += 5.89e-09 * std::cos(2.50543543638 + 3097.88382272579 * t); + result += 5.51e-09 * std::cos(5.28099026956 + 9388.0059094152 * t); + result += 6.96e-09 * std::cos(3.65342150016 + 4804.209275927 * t); + result += 6.69e-09 * std::cos(2.51030077026 + 2388.8940204492 * t); + result += 5.5e-09 * std::cos(0.06883864342 + 20199.094959633 * t); + result += 6.29e-09 * std::cos(4.13350995675 + 45892.730433157 * t); + result += 6.78e-09 * std::cos(6.09190163533 + 135.62532501 * t); + result += 5.93e-09 * std::cos(1.50136257618 + 226858.23855437 * t); + result += 5.42e-09 * std::cos(3.58573645173 + 6148.010769956 * t); + result += 6.82e-09 * std::cos(5.02203067788 + 17253.0411076896 * t); + result += 5.65e-09 * std::cos(4.2930923861 + 11933.3679606696 * t); + result += 4.86e-09 * std::cos(0.77746204893 + 27.4015560968 * t); + result += 5.03e-09 * std::cos(0.58963565969 + 15671.0817594066 * t); + result += 6.16e-09 * std::cos(4.06539884128 + 227.476132789 * t); + result += 5.83e-09 * std::cos(6.12695541996 + 18875.525869774 * t); + result += 5.37e-09 * std::cos(2.1505644098 + 21954.157609398 * t); + result += 6.69e-09 * std::cos(6.06986269566 + 47162.5163546352 * t); + result += 4.75e-09 * std::cos(0.4034384211 + 6915.8595893046 * t); + result += 5.4e-09 * std::cos(2.83444222174 + 5326.7866940208 * t); + result += 5.3e-09 * std::cos(5.26359885263 + 10988.808157535 * t); + result += 5.82e-09 * std::cos(3.24533095664 + 153.7788104848 * t); + result += 6.41e-09 * std::cos(3.24711791371 + 2107.0345075424 * t); + result += 6.21e-09 * std::cos(3.09698523779 + 33019.0211122046 * t); + result += 4.66e-09 * std::cos(3.14982372198 + 10440.2742926036 * t); + result += 4.66e-09 * std::cos(0.90708835657 + 5966.6839803348 * t); + result += 5.28e-09 * std::cos(0.8192645447 + 813.5502839598 * t); + result += 6.03e-09 * std::cos(3.81378921927 + 316428.228673915 * t); + result += 5.59e-09 * std::cos(1.81894804124 + 17996.0311682222 * t); + result += 4.37e-09 * std::cos(2.28625594435 + 6303.8512454838 * t); + result += 5.18e-09 * std::cos(4.86069178322 + 20597.2439630412 * t); + result += 4.24e-09 * std::cos(6.23520018693 + 6489.2613984286 * t); + result += 5.18e-09 * std::cos(6.17617826756 + 0.2438174835 * t); + result += 4.04e-09 * std::cos(5.72804304258 + 5642.1982426092 * t); + result += 4.58e-09 * std::cos(1.34117773915 + 6287.0080032545 * t); + result += 5.48e-09 * std::cos(5.6845445832 + 155427.542936241 * t); + result += 5.47e-09 * std::cos(1.03391472061 + 3646.3503773544 * t); + result += 4.28e-09 * std::cos(4.69800981138 + 846.0828347512 * t); + result += 4.13e-09 * std::cos(6.02520699406 + 6279.4854213396 * t); + result += 5.34e-09 * std::cos(3.03030638223 + 66567.4858652543 * t); + result += 3.83e-09 * std::cos(1.49056949125 + 19800.9459562248 * t); + result += 4.1e-09 * std::cos(5.28319622279 + 18451.078546566 * t); + result += 3.52e-09 * std::cos(4.68891600359 + 4907.3020501456 * t); + result += 4.8e-09 * std::cos(5.36572651091 + 348.924420448 * t); + result += 3.44e-09 * std::cos(5.89157452896 + 6546.1597733642 * t); + result += 3.4e-09 * std::cos(0.3755742644 + 13119.7211028252 * t); + result += 4.34e-09 * std::cos(4.98417785901 + 6702.5604938666 * t); + result += 3.32e-09 * std::cos(2.68902519126 + 29296.6153895786 * t); + result += 4.48e-09 * std::cos(2.16478480251 + 5905.7022420756 * t); + result += 3.44e-09 * std::cos(2.06546633735 + 49.7570254718 * t); + result += 3.15e-09 * std::cos(1.24023811803 + 4061.2192153944 * t); + result += 3.24e-09 * std::cos(2.30897526929 + 5017.508371365 * t); + result += 4.13e-09 * std::cos(0.17171692962 + 6286.6662786432 * t); + result += 4.31e-09 * std::cos(3.86601101393 + 12489.8856287072 * t); + result += 3.49e-09 * std::cos(4.55372342974 + 4933.2084403326 * t); + result += 3.23e-09 * std::cos(0.41971136084 + 10770.8932562618 * t); + result += 3.41e-09 * std::cos(2.68612860807 + 11.0457002639 * t); + result += 3.16e-09 * std::cos(3.52936906658 + 17782.7320727842 * t); + result += 3.15e-09 * std::cos(5.63357264999 + 568.8218740274 * t); + result += 3.4e-09 * std::cos(3.83571212349 + 10660.6869350424 * t); + result += 2.97e-09 * std::cos(0.62691416712 + 20995.3929664494 * t); + result += 4.05e-09 * std::cos(1.00085779471 + 16460.333529525 * t); + result += 4.14e-09 * std::cos(1.21998752076 + 51092.7260508548 * t); + result += 3.36e-09 * std::cos(4.71465945226 + 6179.9830757728 * t); + result += 3.61e-09 * std::cos(3.71227508354 + 28237.2334593894 * t); + result += 3.85e-09 * std::cos(6.21925225757 + 24356.7807886416 * t); + result += 3.27e-09 * std::cos(1.05606504715 + 11919.140866668 * t); + result += 3.27e-09 * std::cos(6.14222420989 + 6254.6266625236 * t); + result += 2.68e-09 * std::cos(2.47224339737 + 664.75604513 * t); + result += 2.69e-09 * std::cos(1.86207884109 + 23141.5583829246 * t); + result += 3.45e-09 * std::cos(0.93461290184 + 6058.7310542895 * t); + result += 2.96e-09 * std::cos(4.5168755718 + 6418.1409300268 * t); + result += 3.53e-09 * std::cos(4.50033653082 + 36949.2308084242 * t); + result += 2.6e-09 * std::cos(4.04963546305 + 6525.8044539654 * t); + result += 2.98e-09 * std::cos(2.20046722622 + 156137.475984799 * t); + result += 2.53e-09 * std::cos(3.49900838384 + 29864.334027309 * t); + result += 2.54e-09 * std::cos(2.44901693835 + 5331.3574437408 * t); + result += 2.96e-09 * std::cos(0.84347588787 + 5729.506447149 * t); + result += 2.98e-09 * std::cos(1.29194706125 + 22805.7355659936 * t); + result += 2.41e-09 * std::cos(2.00721280805 + 16737.5772365966 * t); + result += 3.11e-09 * std::cos(1.23668016334 + 6281.5913772831 * t); + result += 2.4e-09 * std::cos(2.51650377121 + 6245.0481773556 * t); + result += 3.32e-09 * std::cos(3.55576945724 + 7668.6374249425 * t); + result += 2.64e-09 * std::cos(4.44052061202 + 12964.300703391 * t); + result += 2.57e-09 * std::cos(1.79654471948 + 11080.1715789176 * t); + result += 2.6e-09 * std::cos(3.3307759842 + 5888.4499649322 * t); + result += 2.85e-09 * std::cos(0.3088636143 + 11823.1616394502 * t); + result += 2.9e-09 * std::cos(5.70141882483 + 77.673770428 * t); + result += 2.55e-09 * std::cos(4.0093966444 + 5881.4037282342 * t); + result += 2.53e-09 * std::cos(4.73318493678 + 16723.350142595 * t); + result += 2.28e-09 * std::cos(0.95333661324 + 5540.0857894588 * t); + result += 3.19e-09 * std::cos(1.38633229189 + 163096.180361183 * t); + result += 2.24e-09 * std::cos(1.65156322696 + 10027.9031957292 * t); + result += 2.26e-09 * std::cos(0.34106460604 + 17796.9591667858 * t); + result += 2.36e-09 * std::cos(4.19817431922 + 19.66976089979 * t); + result += 2.8e-09 * std::cos(4.1408026897 + 12539.853380183 * t); + result += 2.75e-09 * std::cos(5.50306930248 + 32.5325507914 * t); + result += 2.23e-09 * std::cos(5.23334210294 + 56.8983749356 * t); + result += 2.17e-09 * std::cos(6.08587881787 + 6805.6532680852 * t); + result += 2.8e-09 * std::cos(4.52472044653 + 6016.4688082696 * t); + result += 2.27e-09 * std::cos(5.06509843737 + 6277.552925684 * t); + result += 2.26e-09 * std::cos(5.17755154305 + 11720.0688652316 * t); + result += 2.45e-09 * std::cos(3.96486270306 + 22.7752014508 * t); + result += 2.2e-09 * std::cos(4.7207808197 + 6.62855890001 * t); + result += 2.07e-09 * std::cos(5.71701403951 + 41.5507909848 * t); + result += 2.04e-09 * std::cos(3.9122741125 + 2699.7348193176 * t); + result += 2.09e-09 * std::cos(0.86881969011 + 6321.1035226272 * t); + result += 2e-09 * std::cos(2.11984445273 + 4274.5183108324 * t); + result += 2e-09 * std::cos(5.39839888163 + 6019.9919266186 * t); + result += 2.09e-09 * std::cos(5.67606291663 + 11293.4706743556 * t); + result += 2.52e-09 * std::cos(1.64965729351 + 9380.9596727172 * t); + result += 2.75e-09 * std::cos(5.04826903506 + 73.297125859 * t); + result += 2.08e-09 * std::cos(1.88207277133 + 11300.5842213564 * t); + result += 2.72e-09 * std::cos(0.74640926842 + 1975.492545856 * t); + result += 1.99e-09 * std::cos(3.30836672397 + 22743.4093795164 * t); + result += 2.69e-09 * std::cos(4.48560812155 + 64471.9912417449 * t); + result += 1.92e-09 * std::cos(2.17464236325 + 5863.5912061162 * t); + result += 2.28e-09 * std::cos(5.85373115869 + 128.0188433374 * t); + result += 2.61e-09 * std::cos(2.64321183295 + 55022.9357470744 * t); + result += 2.2e-09 * std::cos(5.75012110079 + 29.429508536 * t); + result += 1.87e-09 * std::cos(4.03230554718 + 467.9649903544 * t); + result += 2e-09 * std::cos(5.60556112058 + 1066.49547719 * t); + result += 2.31e-09 * std::cos(1.09802712785 + 12341.8069042809 * t); + result += 1.99e-09 * std::cos(0.295006252 + 149.5631971346 * t); + result += 2.49e-09 * std::cos(5.10473210814 + 7875.6718636242 * t); + result += 2.08e-09 * std::cos(0.93013835019 + 14919.0178537546 * t); + result += 1.79e-09 * std::cos(0.87104393079 + 12721.572099417 * t); + result += 2.03e-09 * std::cos(1.56920753653 + 28286.9904848612 * t); + result += 1.79e-09 * std::cos(2.47036386443 + 16062.1845261168 * t); + result += 1.98e-09 * std::cos(3.54061588502 + 30.914125635 * t); + result += 1.71e-09 * std::cos(3.45356518113 + 5327.4761083828 * t); + result += 1.83e-09 * std::cos(0.72325421604 + 6272.0301497275 * t); + result += 2.16e-09 * std::cos(2.97174580686 + 19402.7969528166 * t); + result += 1.68e-09 * std::cos(2.51550550242 + 23937.856389741 * t); + result += 1.95e-09 * std::cos(0.09045393425 + 156.4007205024 * t); + result += 1.79e-09 * std::cos(4.4947179809 + 31415.379249957 * t); + result += 2.16e-09 * std::cos(0.42177594328 + 23539.7073863328 * t); + result += 1.89e-09 * std::cos(0.37542530191 + 9814.6041002912 * t); + result += 2.18e-09 * std::cos(2.36835880025 + 16627.3709153772 * t); + result += 1.66e-09 * std::cos(4.23182968446 + 16840.6700108152 * t); + result += 2e-09 * std::cos(2.02153258098 + 16097.6799502826 * t); + result += 1.69e-09 * std::cos(0.91318727 + 95.9792272178 * t); + result += 2.11e-09 * std::cos(5.73370637657 + 151.8972810852 * t); + result += 2.04e-09 * std::cos(0.42643085174 + 515.463871093 * t); + result += 2.12e-09 * std::cos(3.00233538977 + 12043.574281889 * t); + result += 1.92e-09 * std::cos(5.46153589821 + 6379.0550772092 * t); + result += 1.65e-09 * std::cos(1.38698167064 + 4171.4255366138 * t); + result += 1.6e-09 * std::cos(6.23798383332 + 202.2533951741 * t); + result += 2.15e-09 * std::cos(0.20889073407 + 5621.8429232104 * t); + result += 1.81e-09 * std::cos(4.12439203622 + 13341.6743113068 * t); + result += 1.53e-09 * std::cos(1.24460848836 + 29826.3063546732 * t); + result += 1.5e-09 * std::cos(3.12999753018 + 799.8211251654 * t); + result += 1.75e-09 * std::cos(4.55671604437 + 239424.390254353 * t); + result += 1.92e-09 * std::cos(1.33928820063 + 394.6258850592 * t); + result += 1.49e-09 * std::cos(2.65697593276 + 21.335640467 * t); + result += 1.46e-09 * std::cos(5.58021191726 + 412.3710968744 * t); + result += 1.56e-09 * std::cos(3.75650175503 + 12323.4230960088 * t); + result += 1.43e-09 * std::cos(3.75708566606 + 58864.5439181463 * t); + result += 1.43e-09 * std::cos(3.28248547724 + 29.8214381488 * t); + result += 1.44e-09 * std::cos(1.07862546598 + 1265.5674786264 * t); + result += 1.48e-09 * std::cos(0.23389236655 + 10021.8372800994 * t); + result += 1.93e-09 * std::cos(5.92751083086 + 40879.4405046438 * t); + result += 1.4e-09 * std::cos(4.97612440269 + 158.9435177832 * t); + result += 1.48e-09 * std::cos(2.61640453469 + 17157.0618804718 * t); + result += 1.41e-09 * std::cos(3.66871308723 + 26084.0218062162 * t); + result += 1.47e-09 * std::cos(5.09968173403 + 661.232926781 * t); + result += 1.46e-09 * std::cos(4.96885605695 + 57375.8019008462 * t); + result += 1.42e-09 * std::cos(0.78678347839 + 12779.4507954208 * t); + result += 1.34e-09 * std::cos(4.79432636012 + 111.1866422876 * t); + result += 1.4e-09 * std::cos(1.27748013377 + 107.6635239386 * t); + result += 1.69e-09 * std::cos(2.74893543762 + 26735.9452622132 * t); + result += 1.65e-09 * std::cos(3.95288000638 + 6357.8574485587 * t); + result += 1.83e-09 * std::cos(5.43418358741 + 369.6998159404 * t); + result += 1.34e-09 * std::cos(3.09132862833 + 17.812522118 * t); + result += 1.32e-09 * std::cos(3.05633896779 + 22490.9621214934 * t); + result += 1.34e-09 * std::cos(4.09472795832 + 6599.467719648 * t); + result += 1.81e-09 * std::cos(4.22950689891 + 966.9708774356 * t); + result += 1.52e-09 * std::cos(5.28885894415 + 12669.2444742014 * t); + result += 1.5e-09 * std::cos(5.86819430908 + 97238.6275444875 * t); + result += 1.42e-09 * std::cos(5.87266532526 + 22476.7350274918 * t); + result += 1.45e-09 * std::cos(5.07330784304 + 87.30820453981 * t); + result += 1.33e-09 * std::cos(5.65471067133 + 31.9723058168 * t); + result += 1.24e-09 * std::cos(2.83326217072 + 12566.2190102856 * t); + result += 1.35e-09 * std::cos(3.12861731644 + 32217.2001810808 * t); + result += 1.37e-09 * std::cos(0.86487461904 + 9924.8104215106 * t); + result += 1.72e-09 * std::cos(1.98369595114 + 174242.46596405 * t); + result += 1.7e-09 * std::cos(4.41115280254 + 327574.514276781 * t); + result += 1.51e-09 * std::cos(0.46542099527 + 39609.6545831656 * t); + result += 1.48e-09 * std::cos(2.13439571118 + 491.6632924588 * t); + result += 1.53e-09 * std::cos(3.78801830344 + 17363.247428909 * t); + result += 1.65e-09 * std::cos(5.31654110459 + 16943.7627850338 * t); + result += 1.65e-09 * std::cos(4.06747587817 + 58953.145443294 * t); + result += 1.18e-09 * std::cos(0.63846333239 + 6.0659156298 * t); + result += 1.59e-09 * std::cos(0.86086959274 + 221995.028801495 * t); + result += 1.19e-09 * std::cos(5.96432932413 + 1385.8952763362 * t); + result += 1.14e-09 * std::cos(5.16516114595 + 25685.872802808 * t); + result += 1.12e-09 * std::cos(3.39403722178 + 21393.5419698576 * t); + result += 1.12e-09 * std::cos(4.92889233335 + 56.8032621698 * t); + result += 1.19e-09 * std::cos(2.40637635942 + 18635.9284545362 * t); + result += 1.15e-09 * std::cos(0.23374479051 + 418.9243989006 * t); + result += 1.22e-09 * std::cos(0.93575234049 + 24492.4061136516 * t); + result += 1.15e-09 * std::cos(4.58880032176 + 26709.6469424134 * t); + result += 1.3e-09 * std::cos(4.85539251 + 22345.2603761082 * t); + result += 1.4e-09 * std::cos(1.09413073202 + 44809.6502008634 * t); + result += 1.12e-09 * std::cos(6.05401806281 + 433.7117378768 * t); + result += 1.04e-09 * std::cos(1.54931540602 + 127.9515330346 * t); + result += 1.05e-09 * std::cos(4.82620858888 + 33794.5437235286 * t); + result += 1.02e-09 * std::cos(4.12448497391 + 15664.0355227086 * t); + result += 1.07e-09 * std::cos(4.67919356465 + 77690.7595057385 * t); + result += 1.18e-09 * std::cos(4.5232017012 + 19004.6479494084 * t); + result += 1.07e-09 * std::cos(5.71774478555 + 77736.7834305025 * t); + result += 1.43e-09 * std::cos(1.81201813018 + 4214.0690150848 * t); + result += 1.25e-09 * std::cos(1.14419195615 + 625.6701923124 * t); + result += 1.24e-09 * std::cos(3.27736514057 + 12566.08438968 * t); + result += 1.1e-09 * std::cos(1.08682570828 + 2787.0430238574 * t); + result += 1.05e-09 * std::cos(1.78318141871 + 18139.2945014159 * t); + result += 1.02e-09 * std::cos(4.75119578149 + 12242.6462833254 * t); + result += 1.37e-09 * std::cos(1.43510636754 + 86464.6133168312 * t); + result += 1.01e-09 * std::cos(4.91289409429 + 401.6721217572 * t); + result += 1.29e-09 * std::cos(1.23567904485 + 12029.3471878874 * t); + result += 1.38e-09 * std::cos(2.45654707999 + 7576.560073574 * t); + result += 1.03e-09 * std::cos(0.40004073416 + 90279.9231681033 * t); + result += 1.08e-09 * std::cos(0.9898977494 + 5636.0650166766 * t); + result += 1.17e-09 * std::cos(5.17362872063 + 34520.3093093808 * t); + result += 1e-09 * std::cos(3.95534628189 + 5547.1993364596 * t); + result += 9.8e-10 * std::cos(1.28118280598 + 21548.9623692918 * t); + result += 9.7e-10 * std::cos(3.34717130592 + 16310.9790457206 * t); + result += 9.8e-10 * std::cos(4.37041908717 + 34513.2630726828 * t); + result += 1.25e-09 * std::cos(2.7216443296 + 24065.8079227756 * t); + result += 1.02e-09 * std::cos(0.66938025772 + 10239.5838660108 * t); + result += 1.19e-09 * std::cos(1.21689479331 + 1478.8665740644 * t); + result += 9.4e-10 * std::cos(1.99595224256 + 13362.4497067992 * t); + result += 9.4e-10 * std::cos(4.30965982872 + 26880.3198130326 * t); + result += 9.5e-10 * std::cos(2.89807657534 + 34911.412076091 * t); + result += 1.06e-09 * std::cos(1.0015665359 + 16522.6597160022 * t); + result += 9.7e-10 * std::cos(0.89642320201 + 71980.6335747312 * t); + result += 1.16e-09 * std::cos(4.19967201116 + 206.7007372966 * t); + result += 9.9e-10 * std::cos(1.37437847718 + 1039.0266107904 * t); + result += 1.26e-09 * std::cos(3.21642544972 + 305281.943071049 * t); + result += 9.4e-10 * std::cos(0.6899787606 + 7834.1210726394 * t); + result += 9.4e-10 * std::cos(5.58132218606 + 3104.9300594238 * t); + result += 9.5e-10 * std::cos(3.0382374111 + 8982.810669309 * t); + result += 1.08e-09 * std::cos(0.52696637156 + 276.7457718644 * t); + result += 1.24e-09 * std::cos(3.43899862683 + 172146.97134054 * t); + result += 1.02e-09 * std::cos(1.04031728553 + 95143.1329209781 * t); + result += 1.04e-09 * std::cos(3.39218586218 + 290.972865866 * t); + result += 1.1e-09 * std::cos(3.68205877433 + 22380.755800274 * t); + result += 1.17e-09 * std::cos(0.78475956902 + 83286.9142695536 * t); + result += 8.3e-10 * std::cos(0.18241793425 + 15141.390794312 * t); + result += 8.9e-10 * std::cos(4.45371820659 + 792.7748884674 * t); + result += 8.2e-10 * std::cos(4.80703651241 + 6819.8803620868 * t); + result += 8.7e-10 * std::cos(3.43122851097 + 27707.5424942948 * t); + result += 1.01e-09 * std::cos(5.32081603011 + 2301.58581590939 * t); + result += 8.2e-10 * std::cos(0.87060089842 + 10241.2022911672 * t); + result += 8.6e-10 * std::cos(4.61919461931 + 36147.4098773004 * t); + result += 9.5e-10 * std::cos(2.87032884659 + 23020.653086588 * t); + result += 8.8e-10 * std::cos(3.2113316569 + 33326.5787331742 * t); + result += 8e-10 * std::cos(1.84900424847 + 21424.4666443034 * t); + result += 1.01e-09 * std::cos(4.18796434479 + 30666.1549584328 * t); + result += 1.07e-09 * std::cos(5.77864921649 + 34115.1140692746 * t); + result += 1.04e-09 * std::cos(1.08739495962 + 6288.5987742988 * t); + result += 1.1e-09 * std::cos(3.32898859416 + 72140.6286666874 * t); + result += 8.7e-10 * std::cos(4.40657711727 + 142.1786270362 * t); + result += 1.09e-09 * std::cos(1.94546030825 + 24279.1070182136 * t); + result += 8.7e-10 * std::cos(4.32472045435 + 742.9900605326 * t); + result += 1.07e-09 * std::cos(4.91580912547 + 277.0349937414 * t); + result += 8.8e-10 * std::cos(2.10180220766 + 26482.1708096244 * t); + result += 8.6e-10 * std::cos(4.01887374432 + 12491.3701014155 * t); + result += 1.06e-09 * std::cos(5.49092372854 + 62883.3551395136 * t); + result += 8e-10 * std::cos(6.19781316983 + 6709.6740408674 * t); + result += 8.8e-10 * std::cos(2.09872810657 + 238004.524157236 * t); + result += 8.3e-10 * std::cos(4.90662164029 + 51.28033786241 * t); + result += 9.5e-10 * std::cos(4.13387406591 + 18216.443810661 * t); + result += 7.8e-10 * std::cos(6.0694939168 + 148434.534037691 * t); + result += 7.9e-10 * std::cos(3.03048221644 + 838.9692877504 * t); + result += 7.4e-10 * std::cos(5.49813051211 + 29026.4852295078 * t); + result += 7.3e-10 * std::cos(3.05008665738 + 567.7186377304 * t); + result += 8.4e-10 * std::cos(0.46604373274 + 45.1412196366 * t); + result += 9.3e-10 * std::cos(2.52267536308 + 48739.859897083 * t); + result += 7.6e-10 * std::cos(1.76418124905 + 41654.9631159678 * t); + result += 6.7e-10 * std::cos(5.77851227793 + 6311.5250374592 * t); + result += 6.2e-10 * std::cos(3.32967880172 + 15508.6151232744 * t); + result += 7.9e-10 * std::cos(5.59773841328 + 71960.3865832237 * t); + result += 5.7e-10 * std::cos(3.90629505268 + 5999.2165311262 * t); + result += 6.1e-10 * std::cos(0.05695043232 + 7856.89627409019 * t); + result += 6.1e-10 * std::cos(5.63297958433 + 7863.9425107882 * t); + result += 6.5e-10 * std::cos(3.72178394016 + 12573.2652469836 * t); + result += 5.7e-10 * std::cos(4.18217219541 + 26087.9031415742 * t); + result += 6.6e-10 * std::cos(3.92262333487 + 69853.3520756813 * t); + result += 5.3e-10 * std::cos(5.51119362045 + 77710.2483497715 * t); + result += 5.3e-10 * std::cos(4.88573986961 + 77717.2945864695 * t); + result += 6.2e-10 * std::cos(2.88876342225 + 9411.4646150872 * t); + result += 5.1e-10 * std::cos(1.12657183874 + 82576.9812209953 * t); + result += 4.5e-10 * std::cos(2.95671076719 + 24602.612434871 * t); + result += 4e-10 * std::cos(5.55145719241 + 12565.1713789146 * t); + result += 3.9e-10 * std::cos(1.20838190039 + 18842.1140029734 * t); + result += 4.5e-10 * std::cos(3.18590558749 + 45585.1728121874 * t); + result += 4.9e-10 * std::cos(2.44790934886 + 13613.804277336 * t); + return result; +} + +constexpr double getEarthL1(double t) +{ + double result = 0.0; + result += 6283.31966747491; + result += 0.00206058863 * std::cos(2.67823455584 + 6283.0758499914 * t); + result += 4.30343e-05 * std::cos(2.63512650414 + 12566.1516999828 * t); + result += 4.25264e-06 * std::cos(1.59046980729 + 3.523118349 * t); + result += 1.08977e-06 * std::cos(2.96618001993 + 1577.3435424478 * t); + result += 9.3478e-07 * std::cos(2.59212835365 + 18849.2275499742 * t); + result += 1.19261e-06 * std::cos(5.79557487799 + 26.2983197998 * t); + result += 7.2122e-07 * std::cos(1.13846158196 + 529.6909650946 * t); + result += 6.7768e-07 * std::cos(1.87472304791 + 398.1490034082 * t); + result += 6.7327e-07 * std::cos(4.40918235168 + 5507.5532386674 * t); + result += 5.9027e-07 * std::cos(2.8879703846 + 5223.6939198022 * t); + result += 5.5976e-07 * std::cos(2.17471680261 + 155.4203994342 * t); + result += 4.5407e-07 * std::cos(0.39803079805 + 796.2980068164 * t); + result += 3.6369e-07 * std::cos(0.46624739835 + 775.522611324 * t); + result += 2.8958e-07 * std::cos(2.64707383882 + 7.1135470008 * t); + result += 1.9097e-07 * std::cos(1.84628332577 + 5486.777843175 * t); + result += 2.0844e-07 * std::cos(5.34138275149 + 0.9803210682 * t); + result += 1.8508e-07 * std::cos(4.96855124577 + 213.299095438 * t); + result += 1.6233e-07 * std::cos(0.03216483047 + 2544.3144198834 * t); + result += 1.7293e-07 * std::cos(2.99116864949 + 6275.9623029906 * t); + result += 1.5832e-07 * std::cos(1.43049285325 + 2146.1654164752 * t); + result += 1.4615e-07 * std::cos(1.20532366323 + 10977.078804699 * t); + result += 1.1877e-07 * std::cos(3.25804815607 + 5088.6288397668 * t); + result += 1.1514e-07 * std::cos(2.07502418155 + 4694.0029547076 * t); + result += 9.721e-08 * std::cos(4.23925472239 + 1349.8674096588 * t); + result += 9.969e-08 * std::cos(1.30262991097 + 6286.5989683404 * t); + result += 9.452e-08 * std::cos(2.69957062864 + 242.728603974 * t); + result += 1.2461e-07 * std::cos(2.83432285512 + 1748.016413067 * t); + result += 1.1808e-07 * std::cos(5.2737979048 + 1194.4470102246 * t); + result += 8.577e-08 * std::cos(5.64475868067 + 951.7184062506 * t); + result += 1.0641e-07 * std::cos(0.76614199202 + 553.5694028424 * t); + result += 7.576e-08 * std::cos(5.30062664886 + 2352.8661537718 * t); + result += 5.834e-08 * std::cos(1.76649917904 + 1059.3819301892 * t); + result += 6.385e-08 * std::cos(2.65033984967 + 9437.762934887 * t); + result += 5.223e-08 * std::cos(5.66135767624 + 71430.6956181291 * t); + result += 5.305e-08 * std::cos(0.90857521574 + 3154.6870848956 * t); + result += 6.101e-08 * std::cos(4.66632584188 + 4690.4798363586 * t); + result += 4.33e-08 * std::cos(0.24102555403 + 6812.766815086 * t); + result += 5.041e-08 * std::cos(1.42490103709 + 6438.4962494256 * t); + result += 4.259e-08 * std::cos(0.77355900599 + 10447.3878396044 * t); + result += 5.198e-08 * std::cos(1.85353197345 + 801.8209311238 * t); + result += 3.744e-08 * std::cos(2.00119516488 + 8031.0922630584 * t); + result += 3.558e-08 * std::cos(2.42901552681 + 14143.4952424306 * t); + result += 3.372e-08 * std::cos(3.86210700128 + 1592.5960136328 * t); + result += 3.374e-08 * std::cos(0.88776219727 + 12036.4607348882 * t); + result += 3.175e-08 * std::cos(3.18785710594 + 4705.7323075436 * t); + result += 3.221e-08 * std::cos(0.61599835472 + 8429.2412664666 * t); + result += 4.132e-08 * std::cos(5.23992859705 + 7084.8967811152 * t); + result += 2.97e-08 * std::cos(6.07026318493 + 4292.3308329504 * t); + result += 2.9e-08 * std::cos(2.32464208411 + 20.3553193988 * t); + result += 3.504e-08 * std::cos(4.79975694359 + 6279.5527316424 * t); + result += 2.95e-08 * std::cos(1.43108874817 + 5746.271337896 * t); + result += 2.697e-08 * std::cos(4.80368225199 + 7234.794256242 * t); + result += 2.531e-08 * std::cos(6.22290682655 + 6836.6452528338 * t); + result += 2.745e-08 * std::cos(0.93466065396 + 5760.4984318976 * t); + result += 3.25e-08 * std::cos(3.39954640038 + 7632.9432596502 * t); + result += 2.277e-08 * std::cos(5.00277837672 + 17789.845619785 * t); + result += 2.075e-08 * std::cos(3.95534978634 + 10213.285546211 * t); + result += 2.061e-08 * std::cos(2.22411683077 + 5856.4776591154 * t); + result += 2.252e-08 * std::cos(5.67166499885 + 11499.6562227928 * t); + result += 2.148e-08 * std::cos(5.20184578235 + 11513.8833167944 * t); + result += 1.886e-08 * std::cos(0.53198320577 + 3340.6124266998 * t); + result += 1.875e-08 * std::cos(4.73511970207 + 83996.8473181119 * t); + result += 2.06e-08 * std::cos(2.54987293999 + 25132.3033999656 * t); + result += 1.794e-08 * std::cos(1.47435409831 + 4164.311989613 * t); + result += 1.778e-08 * std::cos(3.02473091781 + 5.5229243074 * t); + result += 2.029e-08 * std::cos(0.90960209983 + 6256.7775301916 * t); + result += 2.075e-08 * std::cos(2.26767270157 + 522.5774180938 * t); + result += 1.772e-08 * std::cos(3.02622802353 + 5753.3848848968 * t); + result += 1.569e-08 * std::cos(6.12410242782 + 5216.5803728014 * t); + result += 1.59e-08 * std::cos(4.63713748247 + 3.2863574178 * t); + result += 1.542e-08 * std::cos(4.20004448567 + 13367.9726311066 * t); + result += 1.427e-08 * std::cos(1.19088061711 + 3894.1818295422 * t); + result += 1.375e-08 * std::cos(3.09301252193 + 135.0650800354 * t); + result += 1.359e-08 * std::cos(4.24532506641 + 426.598190876 * t); + result += 1.34e-08 * std::cos(5.76511818622 + 6040.3472460174 * t); + result += 1.284e-08 * std::cos(3.08524663344 + 5643.1785636774 * t); + result += 1.25e-08 * std::cos(3.07748157144 + 11926.2544136688 * t); + result += 1.551e-08 * std::cos(3.07665451458 + 6681.2248533996 * t); + result += 1.268e-08 * std::cos(2.09196018331 + 6290.1893969922 * t); + result += 1.144e-08 * std::cos(3.24444699514 + 12168.0026965746 * t); + result += 1.248e-08 * std::cos(3.44504937285 + 536.8045120954 * t); + result += 1.118e-08 * std::cos(2.31829670425 + 16730.4636895958 * t); + result += 1.105e-08 * std::cos(5.31966001019 + 23.8784377478 * t); + result += 1.051e-08 * std::cos(3.75015946014 + 7860.4193924392 * t); + result += 1.025e-08 * std::cos(2.44688534235 + 1990.745017041 * t); + result += 9.62e-09 * std::cos(0.81771017882 + 3.881335358 * t); + result += 9.1e-09 * std::cos(0.41727865299 + 7079.3738568078 * t); + result += 8.83e-09 * std::cos(5.16833917651 + 11790.6290886588 * t); + result += 9.57e-09 * std::cos(4.07673573735 + 6127.6554505572 * t); + result += 1.11e-08 * std::cos(3.90096793825 + 11506.7697697936 * t); + result += 8.02e-09 * std::cos(3.88778875582 + 10973.55568635 * t); + result += 7.8e-09 * std::cos(2.39934293755 + 1589.0728952838 * t); + result += 7.58e-09 * std::cos(1.30034364248 + 103.0927742186 * t); + result += 7.49e-09 * std::cos(4.962758033 + 6496.3749454294 * t); + result += 7.65e-09 * std::cos(3.36312388424 + 36.0278666774 * t); + result += 9.15e-09 * std::cos(5.41543742089 + 206.1855484372 * t); + result += 7.76e-09 * std::cos(2.57589093871 + 11371.7046897582 * t); + result += 7.72e-09 * std::cos(3.98369209464 + 955.5997416086 * t); + result += 7.49e-09 * std::cos(5.17890001805 + 10969.9652576982 * t); + result += 8.06e-09 * std::cos(0.34218864254 + 9917.6968745098 * t); + result += 7.28e-09 * std::cos(5.20962563787 + 38.0276726358 * t); + result += 6.85e-09 * std::cos(2.77592961854 + 20.7753954924 * t); + result += 6.36e-09 * std::cos(4.28242193632 + 28.4491874678 * t); + result += 6.08e-09 * std::cos(5.63278508906 + 10984.1923516998 * t); + result += 7.04e-09 * std::cos(5.60738823665 + 3738.761430108 * t); + result += 6.85e-09 * std::cos(0.38876148682 + 15.252471185 * t); + result += 6.01e-09 * std::cos(0.73489602442 + 419.4846438752 * t); + result += 7.16e-09 * std::cos(2.65279791438 + 6309.3741697912 * t); + result += 5.84e-09 * std::cos(5.54502568227 + 17298.1823273262 * t); + result += 6.5e-09 * std::cos(1.13379656406 + 7058.5984613154 * t); + result += 6.88e-09 * std::cos(2.59683891779 + 3496.032826134 * t); + result += 4.85e-09 * std::cos(0.44467180946 + 12352.8526045448 * t); + result += 5.28e-09 * std::cos(2.74936967681 + 3930.2096962196 * t); + result += 5.97e-09 * std::cos(5.27668281777 + 10575.4066829418 * t); + result += 5.83e-09 * std::cos(3.1892906781 + 4732.0306273434 * t); + result += 5.26e-09 * std::cos(5.01697321546 + 5884.9268465832 * t); + result += 5.4e-09 * std::cos(1.29175137075 + 640.8776073822 * t); + result += 4.73e-09 * std::cos(5.4995330697 + 5230.807466803 * t); + result += 4.06e-09 * std::cos(5.21248452189 + 220.4126424388 * t); + result += 3.95e-09 * std::cos(1.87474483222 + 16200.7727245012 * t); + result += 3.7e-09 * std::cos(3.84921354713 + 18073.7049386502 * t); + result += 3.67e-09 * std::cos(0.88533542778 + 6283.14316029419 * t); + result += 3.79e-09 * std::cos(0.37983009325 + 10177.2576795336 * t); + result += 3.56e-09 * std::cos(3.84145204913 + 11712.9553182308 * t); + result += 3.74e-09 * std::cos(5.01577520608 + 7.046236698 * t); + result += 3.81e-09 * std::cos(4.30250406634 + 6062.6632075526 * t); + result += 4.71e-09 * std::cos(0.86381834647 + 6069.7767545534 * t); + result += 3.67e-09 * std::cos(1.32943839763 + 6283.0085396886 * t); + result += 4.6e-09 * std::cos(5.19667219575 + 6284.0561710596 * t); + result += 3.33e-09 * std::cos(5.54256205741 + 4686.8894077068 * t); + result += 3.41e-09 * std::cos(4.36522989934 + 7238.6755916 * t); + result += 3.36e-09 * std::cos(4.00205876835 + 3097.88382272579 * t); + result += 3.59e-09 * std::cos(6.22679790284 + 245.8316462294 * t); + result += 3.07e-09 * std::cos(2.35299010924 + 170.6728706192 * t); + result += 3.43e-09 * std::cos(3.77164927143 + 6076.8903015542 * t); + result += 2.96e-09 * std::cos(5.44152227481 + 17260.1546546904 * t); + result += 3.28e-09 * std::cos(0.13837875384 + 11015.1064773348 * t); + result += 2.68e-09 * std::cos(1.1390455063 + 12569.6748183318 * t); + result += 2.63e-09 * std::cos(0.00538633678 + 4136.9104335162 * t); + result += 2.82e-09 * std::cos(5.0439983748 + 7477.522860216 * t); + result += 2.88e-09 * std::cos(3.13401177517 + 12559.038152982 * t); + result += 2.59e-09 * std::cos(0.93882269387 + 5642.1982426092 * t); + result += 2.92e-09 * std::cos(1.98420020514 + 12132.439962106 * t); + result += 2.47e-09 * std::cos(3.84244798532 + 5429.8794682394 * t); + result += 2.45e-09 * std::cos(5.70467521726 + 65147.6197681377 * t); + result += 2.41e-09 * std::cos(0.99480969552 + 3634.6210245184 * t); + result += 2.46e-09 * std::cos(3.06168069935 + 110.2063212194 * t); + result += 2.39e-09 * std::cos(6.11855909114 + 11856.2186514245 * t); + result += 2.63e-09 * std::cos(0.66348415419 + 21228.3920235458 * t); + result += 2.62e-09 * std::cos(1.51070507866 + 12146.6670561076 * t); + result += 2.3e-09 * std::cos(1.75927314884 + 9779.1086761254 * t); + result += 2.23e-09 * std::cos(2.00967043606 + 6172.869528772 * t); + result += 2.46e-09 * std::cos(1.10411690865 + 6282.0955289232 * t); + result += 2.21e-09 * std::cos(3.03945240854 + 8635.9420037632 * t); + result += 2.14e-09 * std::cos(4.03840869663 + 14314.1681130498 * t); + result += 2.36e-09 * std::cos(5.4691507058 + 13916.0191096416 * t); + result += 2.24e-09 * std::cos(4.68408089456 + 24072.9214697764 * t); + result += 2.12e-09 * std::cos(2.13695625494 + 5849.3641121146 * t); + result += 2.07e-09 * std::cos(3.07724246401 + 11.729352836 * t); + result += 2.07e-09 * std::cos(6.10306282747 + 23543.2305046818 * t); + result += 2.66e-09 * std::cos(1.00709566823 + 2388.8940204492 * t); + result += 2.17e-09 * std::cos(6.27837036335 + 17267.2682016912 * t); + result += 2.04e-09 * std::cos(2.34615348695 + 266.6070417218 * t); + result += 1.95e-09 * std::cos(5.55015549753 + 6133.5126528568 * t); + result += 1.88e-09 * std::cos(2.52667166175 + 6525.8044539654 * t); + result += 1.85e-09 * std::cos(0.90960768344 + 18319.5365848796 * t); + result += 1.77e-09 * std::cos(1.73429218289 + 154717.609887683 * t); + result += 1.87e-09 * std::cos(4.76483647432 + 4535.0594369244 * t); + result += 1.86e-09 * std::cos(4.63080493407 + 10440.2742926036 * t); + result += 2.15e-09 * std::cos(2.8125545456 + 7342.4577801806 * t); + result += 1.72e-09 * std::cos(1.45551888559 + 9225.539273283 * t); + result += 1.62e-09 * std::cos(3.30661909388 + 639.897286314 * t); + result += 1.68e-09 * std::cos(2.17671416605 + 27.4015560968 * t); + result += 1.6e-09 * std::cos(1.68164180475 + 15110.4661198662 * t); + result += 1.58e-09 * std::cos(0.13519771874 + 13095.8426650774 * t); + result += 1.83e-09 * std::cos(0.56281322071 + 13517.8701062334 * t); + result += 1.79e-09 * std::cos(3.58450811616 + 87.30820453981 * t); + result += 1.52e-09 * std::cos(2.84070476818 + 5650.2921106782 * t); + result += 1.82e-09 * std::cos(0.44065530624 + 17253.0411076896 * t); + result += 1.6e-09 * std::cos(5.95767264171 + 4701.1165017084 * t); + result += 1.42e-09 * std::cos(1.4629013752 + 11087.2851259184 * t); + result += 1.42e-09 * std::cos(2.04464036087 + 20426.571092422 * t); + result += 1.31e-09 * std::cos(5.40912137746 + 2699.7348193176 * t); + result += 1.44e-09 * std::cos(2.07312090485 + 25158.6017197654 * t); + result += 1.47e-09 * std::cos(6.15106982168 + 9623.6882766912 * t); + result += 1.41e-09 * std::cos(5.55739979498 + 10454.5013866052 * t); + result += 1.35e-09 * std::cos(0.06098110407 + 16723.350142595 * t); + result += 1.24e-09 * std::cos(5.81218025669 + 17256.6315363414 * t); + result += 1.24e-09 * std::cos(2.36293551623 + 4933.2084403326 * t); + result += 1.26e-09 * std::cos(3.47435905118 + 22483.8485744926 * t); + result += 1.59e-09 * std::cos(5.63954754618 + 5729.506447149 * t); + result += 1.23e-09 * std::cos(3.92815963256 + 17996.0311682222 * t); + result += 1.48e-09 * std::cos(3.02509280598 + 1551.045222648 * t); + result += 1.2e-09 * std::cos(5.91904349732 + 6206.8097787158 * t); + result += 1.34e-09 * std::cos(3.11122937825 + 21954.157609398 * t); + result += 1.19e-09 * std::cos(5.5214112345 + 709.9330485583 * t); + result += 1.22e-09 * std::cos(3.00813429479 + 19800.9459562248 * t); + result += 1.27e-09 * std::cos(1.37618620001 + 14945.3161735544 * t); + result += 1.41e-09 * std::cos(2.56889468729 + 1052.2683831884 * t); + result += 1.23e-09 * std::cos(2.83671175442 + 11919.140866668 * t); + result += 1.18e-09 * std::cos(0.81934438215 + 5331.3574437408 * t); + result += 1.51e-09 * std::cos(2.68731829165 + 11769.8536931664 * t); + result += 1.19e-09 * std::cos(5.08835797638 + 5481.2549188676 * t); + result += 1.53e-09 * std::cos(2.46021790779 + 11933.3679606696 * t); + result += 1.08e-09 * std::cos(1.04936452145 + 11403.676995575 * t); + result += 1.28e-09 * std::cos(0.99794735107 + 8827.3902698748 * t); + result += 1.44e-09 * std::cos(2.54869747042 + 227.476132789 * t); + result += 1.5e-09 * std::cos(4.50631437136 + 2379.1644735716 * t); + result += 1.07e-09 * std::cos(1.79272017026 + 13119.7211028252 * t); + result += 1.07e-09 * std::cos(4.43556814486 + 18422.6293590982 * t); + result += 1.09e-09 * std::cos(0.29269062317 + 16737.5772365966 * t); + result += 1.41e-09 * std::cos(3.18979826258 + 6262.300454499 * t); + result += 1.22e-09 * std::cos(4.23040027813 + 29.429508536 * t); + result += 1.11e-09 * std::cos(5.16954029551 + 17782.7320727842 * t); + result += 1e-09 * std::cos(3.52213872761 + 18052.9295431578 * t); + result += 1.08e-09 * std::cos(1.08514212991 + 16858.4825329332 * t); + result += 1.06e-09 * std::cos(1.9608524841 + 74.7815985673 * t); + result += 1.1e-09 * std::cos(2.30582372873 + 16460.333529525 * t); + result += 9.7e-10 * std::cos(3.5091894021 + 5333.9002410216 * t); + result += 9.9e-10 * std::cos(3.56417337974 + 735.8765135318 * t); + result += 9.4e-10 * std::cos(5.01857894228 + 3128.3887650958 * t); + result += 9.7e-10 * std::cos(1.65579893894 + 533.2140834436 * t); + result += 9.2e-10 * std::cos(0.89217162285 + 29296.6153895786 * t); + result += 1.23e-09 * std::cos(3.16062050433 + 9380.9596727172 * t); + result += 1.02e-09 * std::cos(1.20493500565 + 23020.653086588 * t); + result += 8.8e-10 * std::cos(2.21296088224 + 12721.572099417 * t); + result += 8.9e-10 * std::cos(1.5426472031 + 20199.094959633 * t); + result += 1.13e-09 * std::cos(4.8332070787 + 16496.3613962024 * t); + result += 1.21e-09 * std::cos(6.19860353182 + 9388.0059094152 * t); + result += 8.9e-10 * std::cos(4.08082274765 + 22805.7355659936 * t); + result += 9.8e-10 * std::cos(1.0918183283 + 12043.574281889 * t); + result += 8.6e-10 * std::cos(1.13655027605 + 143571.324284816 * t); + result += 8.8e-10 * std::cos(5.96980472191 + 107.6635239386 * t); + result += 8.2e-10 * std::cos(5.01340404594 + 22003.9146348698 * t); + result += 9.4e-10 * std::cos(1.69615700473 + 23006.4259925864 * t); + result += 8.1e-10 * std::cos(3.00657814365 + 2118.7638603784 * t); + result += 9.8e-10 * std::cos(1.39215287161 + 8662.240323563 * t); + result += 7.7e-10 * std::cos(3.3355519084 + 15720.8387848784 * t); + result += 8.2e-10 * std::cos(5.86880116464 + 2787.0430238574 * t); + result += 7.6e-10 * std::cos(5.67183650604 + 14.2270940016 * t); + result += 8.1e-10 * std::cos(6.16619455699 + 1039.0266107904 * t); + result += 7.6e-10 * std::cos(3.21449884756 + 111.1866422876 * t); + result += 7.8e-10 * std::cos(1.37531518377 + 21947.1113727 * t); + result += 7.4e-10 * std::cos(3.58814195051 + 11609.8625440122 * t); + result += 7.7e-10 * std::cos(4.84846488388 + 22743.4093795164 * t); + result += 9e-10 * std::cos(1.48869013606 + 15671.0817594066 * t); + result += 8.2e-10 * std::cos(3.48618399109 + 29088.811415985 * t); + result += 6.9e-10 * std::cos(3.55746476593 + 4590.910180489 * t); + result += 6.9e-10 * std::cos(1.93625656075 + 135.62532501 * t); + result += 7e-10 * std::cos(2.66548322237 + 18875.525869774 * t); + result += 6.9e-10 * std::cos(5.41478093731 + 26735.9452622132 * t); + result += 7.9e-10 * std::cos(5.15154513662 + 12323.4230960088 * t); + result += 9.4e-10 * std::cos(3.62899392448 + 77713.7714681205 * t); + result += 7.8e-10 * std::cos(4.17011182047 + 1066.49547719 * t); + result += 7.1e-10 * std::cos(3.89435637865 + 22779.4372461938 * t); + result += 6.3e-10 * std::cos(4.53968787714 + 8982.810669309 * t); + result += 6.9e-10 * std::cos(0.96028230548 + 14919.0178537546 * t); + result += 7.6e-10 * std::cos(3.29092216589 + 2942.4634232916 * t); + result += 6.3e-10 * std::cos(4.09167842893 + 16062.1845261168 * t); + result += 6.5e-10 * std::cos(3.34580407184 + 51.28033786241 * t); + result += 6.5e-10 * std::cos(5.75757544877 + 52670.0695933026 * t); + result += 6.8e-10 * std::cos(5.75884067555 + 21424.4666443034 * t); + result += 5.7e-10 * std::cos(5.4512239985 + 12592.4500197826 * t); + result += 5.7e-10 * std::cos(5.25043362558 + 20995.3929664494 * t); + result += 7.3e-10 * std::cos(0.53299090807 + 2301.58581590939 * t); + result += 7e-10 * std::cos(4.31243357502 + 19402.7969528166 * t); + result += 6.7e-10 * std::cos(2.53852336668 + 377.3736079158 * t); + result += 5.6e-10 * std::cos(3.20816844695 + 24889.5747959916 * t); + result += 5.3e-10 * std::cos(3.17816599142 + 18451.078546566 * t); + result += 5.3e-10 * std::cos(3.61529270216 + 77.673770428 * t); + result += 5.3e-10 * std::cos(0.45467549335 + 30666.1549584328 * t); + result += 6.1e-10 * std::cos(0.14807288453 + 23013.5395395872 * t); + result += 5.1e-10 * std::cos(3.32803972907 + 56.8983749356 * t); + result += 5.2e-10 * std::cos(3.41177624177 + 23141.5583829246 * t); + result += 5.8e-10 * std::cos(3.13638677202 + 309.2783226558 * t); + result += 7e-10 * std::cos(2.50592323465 + 31415.379249957 * t); + result += 5.2e-10 * std::cos(5.10673376738 + 17796.9591667858 * t); + result += 6.7e-10 * std::cos(6.27917920454 + 22345.2603761082 * t); + result += 5e-10 * std::cos(0.42577644151 + 25685.872802808 * t); + result += 4.8e-10 * std::cos(0.70204553333 + 1162.4747044078 * t); + result += 6.6e-10 * std::cos(3.64350022359 + 15265.8865193004 * t); + result += 5e-10 * std::cos(5.7438291744 + 19.66976089979 * t); + result += 5e-10 * std::cos(4.69825387775 + 28237.2334593894 * t); + result += 4.7e-10 * std::cos(5.74015846442 + 12139.5535091068 * t); + result += 5.4e-10 * std::cos(1.97301333704 + 23581.2581773176 * t); + result += 4.9e-10 * std::cos(4.98223579027 + 10021.8372800994 * t); + result += 4.6e-10 * std::cos(5.41431705539 + 33019.0211122046 * t); + result += 5.1e-10 * std::cos(1.23882053879 + 12539.853380183 * t); + result += 4.6e-10 * std::cos(2.41369976086 + 98068.5367163054 * t); + result += 4.4e-10 * std::cos(0.80750593746 + 167283.761587666 * t); + result += 4.5e-10 * std::cos(4.39613584445 + 433.7117378768 * t); + result += 4.4e-10 * std::cos(2.57358208785 + 12964.300703391 * t); + result += 4.6e-10 * std::cos(0.26142733448 + 11.0457002639 * t); + result += 4.5e-10 * std::cos(2.46230645202 + 51868.2486621788 * t); + result += 4.8e-10 * std::cos(0.89551707131 + 56600.2792895222 * t); + result += 5.7e-10 * std::cos(1.8641670701 + 25287.7237993998 * t); + result += 4.2e-10 * std::cos(5.26377513431 + 26084.0218062162 * t); + result += 4.9e-10 * std::cos(3.17757670611 + 6303.8512454838 * t); + result += 5.2e-10 * std::cos(3.65266055509 + 7872.1487452752 * t); + result += 4e-10 * std::cos(1.81891629936 + 34596.3646546524 * t); + result += 4.3e-10 * std::cos(1.94164978061 + 1903.4368125012 * t); + result += 4.1e-10 * std::cos(0.74461854136 + 23937.856389741 * t); + result += 4.8e-10 * std::cos(6.26034008181 + 28286.9904848612 * t); + result += 4.5e-10 * std::cos(5.4557501753 + 60530.4889857418 * t); + result += 4e-10 * std::cos(2.92105728682 + 21548.9623692918 * t); + result += 4e-10 * std::cos(0.04502010161 + 38526.574350872 * t); + result += 5.3e-10 * std::cos(3.64791042082 + 11925.2740926006 * t); + result += 4.1e-10 * std::cos(5.04048954693 + 27832.0382192832 * t); + result += 4.2e-10 * std::cos(5.19292937193 + 19004.6479494084 * t); + result += 4e-10 * std::cos(2.57120233428 + 24356.7807886416 * t); + result += 3.8e-10 * std::cos(3.49190341464 + 226858.23855437 * t); + result += 3.9e-10 * std::cos(4.61184303844 + 95.9792272178 * t); + result += 4.3e-10 * std::cos(2.20648228147 + 13521.7514415914 * t); + result += 4e-10 * std::cos(5.83461945819 + 16193.6591775004 * t); + result += 4.5e-10 * std::cos(3.73714372195 + 7875.6718636242 * t); + result += 4.3e-10 * std::cos(1.14078465002 + 49.7570254718 * t); + result += 3.7e-10 * std::cos(1.29390383811 + 310.8407988684 * t); + result += 3.8e-10 * std::cos(0.9597092595 + 664.75604513 * t); + result += 3.7e-10 * std::cos(4.27532649462 + 6709.6740408674 * t); + result += 3.8e-10 * std::cos(2.20108541046 + 28628.3362260996 * t); + result += 3.9e-10 * std::cos(0.85957361635 + 16522.6597160022 * t); + result += 4e-10 * std::cos(4.35214003837 + 48739.859897083 * t); + result += 3.6e-10 * std::cos(1.68167662194 + 10344.2950653858 * t); + result += 4e-10 * std::cos(5.13217319067 + 15664.0355227086 * t); + result += 3.6e-10 * std::cos(3.72187132496 + 30774.5016425748 * t); + result += 3.6e-10 * std::cos(3.32158458257 + 16207.886271502 * t); + result += 4.5e-10 * std::cos(3.94202418608 + 10988.808157535 * t); + result += 3.9e-10 * std::cos(1.51948786199 + 12029.3471878874 * t); + result += 2.6e-10 * std::cos(3.8768588318 + 6262.7205305926 * t); + result += 2.4e-10 * std::cos(4.91804163466 + 19651.048481098 * t); + result += 2.3e-10 * std::cos(0.29300197709 + 13362.4497067992 * t); + result += 2.1e-10 * std::cos(3.18605672363 + 6277.552925684 * t); + result += 2.1e-10 * std::cos(6.07546891132 + 18139.2945014159 * t); + result += 2.2e-10 * std::cos(2.31199937177 + 6303.4311693902 * t); + result += 2.1e-10 * std::cos(3.58418394393 + 18209.3302636602 * t); + result += 2.6e-10 * std::cos(2.068012969 + 12573.2652469836 * t); + result += 2.1e-10 * std::cos(1.56857722317 + 13341.6743113068 * t); + result += 2.4e-10 * std::cos(5.72605158675 + 29864.334027309 * t); + result += 2.4e-10 * std::cos(1.40237993205 + 14712.317116458 * t); + result += 2.5e-10 * std::cos(5.71466092822 + 25934.1243310894 * t); + return result; +} + +constexpr double getEarthL2(double t) +{ + double result = 0.0; + result += 0.0005291887; + result += 8.719837e-05 * std::cos(1.07209665242 + 6283.0758499914 * t); + result += 3.09125e-06 * std::cos(0.86728818832 + 12566.1516999828 * t); + result += 2.7339e-07 * std::cos(0.05297871691 + 3.523118349 * t); + result += 1.6334e-07 * std::cos(5.18826691036 + 26.2983197998 * t); + result += 1.5752e-07 * std::cos(3.6845788943 + 155.4203994342 * t); + result += 9.541e-08 * std::cos(0.75742297675 + 18849.2275499742 * t); + result += 8.937e-08 * std::cos(2.05705419118 + 77713.7714681205 * t); + result += 6.952e-08 * std::cos(0.8267330541 + 775.522611324 * t); + result += 5.064e-08 * std::cos(4.66284525271 + 1577.3435424478 * t); + result += 4.061e-08 * std::cos(1.03057162962 + 7.1135470008 * t); + result += 3.463e-08 * std::cos(5.14074632811 + 796.2980068164 * t); + result += 3.169e-08 * std::cos(6.05291851171 + 5507.5532386674 * t); + result += 3.02e-08 * std::cos(1.19246506441 + 242.728603974 * t); + result += 2.886e-08 * std::cos(6.11652627155 + 529.6909650946 * t); + result += 3.81e-08 * std::cos(3.4405080349 + 5573.1428014331 * t); + result += 2.714e-08 * std::cos(0.30637881025 + 398.1490034082 * t); + result += 2.371e-08 * std::cos(4.38118838167 + 5223.6939198022 * t); + result += 2.538e-08 * std::cos(2.27992810679 + 553.5694028424 * t); + result += 2.079e-08 * std::cos(3.75435330484 + 0.9803210682 * t); + result += 1.675e-08 * std::cos(0.90216407959 + 951.7184062506 * t); + result += 1.534e-08 * std::cos(5.75900462759 + 1349.8674096588 * t); + result += 1.224e-08 * std::cos(2.97328088405 + 2146.1654164752 * t); + result += 1.449e-08 * std::cos(4.3641591397 + 1748.016413067 * t); + result += 1.341e-08 * std::cos(3.72061130861 + 1194.4470102246 * t); + result += 1.254e-08 * std::cos(2.94846826628 + 6438.4962494256 * t); + result += 9.99e-09 * std::cos(5.98640014468 + 6286.5989683404 * t); + result += 9.17e-09 * std::cos(4.79788687522 + 5088.6288397668 * t); + result += 8.28e-09 * std::cos(3.31321076572 + 213.299095438 * t); + result += 1.103e-08 * std::cos(1.27104454479 + 161000.685737674 * t); + result += 7.62e-09 * std::cos(3.41582762988 + 5486.777843175 * t); + result += 1.044e-08 * std::cos(0.60409577691 + 3154.6870848956 * t); + result += 8.87e-09 * std::cos(5.23465144638 + 7084.8967811152 * t); + result += 6.45e-09 * std::cos(1.60096192515 + 2544.3144198834 * t); + result += 6.81e-09 * std::cos(3.43155669169 + 4694.0029547076 * t); + result += 6.05e-09 * std::cos(2.47806340546 + 10977.078804699 * t); + result += 7.06e-09 * std::cos(6.19393222575 + 4690.4798363586 * t); + result += 6.43e-09 * std::cos(1.98042503148 + 801.8209311238 * t); + result += 5.02e-09 * std::cos(1.44394375363 + 6836.6452528338 * t); + result += 4.9e-09 * std::cos(2.34129524194 + 1592.5960136328 * t); + result += 4.58e-09 * std::cos(1.30876448575 + 4292.3308329504 * t); + result += 4.31e-09 * std::cos(0.03526421494 + 7234.794256242 * t); + result += 3.79e-09 * std::cos(3.17030522615 + 6309.3741697912 * t); + result += 3.48e-09 * std::cos(0.99049550009 + 6040.3472460174 * t); + result += 3.86e-09 * std::cos(1.57019797263 + 71430.6956181291 * t); + result += 3.47e-09 * std::cos(0.67013291338 + 1059.3819301892 * t); + result += 4.58e-09 * std::cos(3.81499443681 + 149854.400134808 * t); + result += 3.02e-09 * std::cos(1.91760044838 + 10447.3878396044 * t); + result += 3.07e-09 * std::cos(3.55343347416 + 8031.0922630584 * t); + result += 3.95e-09 * std::cos(4.93701776616 + 7632.9432596502 * t); + result += 3.14e-09 * std::cos(3.18093696547 + 2352.8661537718 * t); + result += 2.82e-09 * std::cos(4.41936437052 + 9437.762934887 * t); + result += 2.76e-09 * std::cos(2.71314254553 + 3894.1818295422 * t); + result += 2.98e-09 * std::cos(2.5203747421 + 6127.6554505572 * t); + result += 2.3e-09 * std::cos(1.37790215549 + 4705.7323075436 * t); + result += 2.52e-09 * std::cos(0.55330133471 + 6279.5527316424 * t); + result += 2.55e-09 * std::cos(5.26570187369 + 6812.766815086 * t); + result += 2.75e-09 * std::cos(0.67264264272 + 25132.3033999656 * t); + result += 1.78e-09 * std::cos(0.92820785174 + 1990.745017041 * t); + result += 2.21e-09 * std::cos(0.63897368842 + 6256.7775301916 * t); + result += 1.55e-09 * std::cos(0.77319790838 + 14143.4952424306 * t); + result += 1.5e-09 * std::cos(2.40470465561 + 426.598190876 * t); + result += 1.96e-09 * std::cos(6.06877865012 + 640.8776073822 * t); + result += 1.37e-09 * std::cos(2.21679460145 + 8429.2412664666 * t); + result += 1.27e-09 * std::cos(3.26094223174 + 17789.845619785 * t); + result += 1.28e-09 * std::cos(5.47237279946 + 12036.4607348882 * t); + result += 1.22e-09 * std::cos(2.16291082757 + 10213.285546211 * t); + result += 1.18e-09 * std::cos(0.45789822268 + 7058.5984613154 * t); + result += 1.41e-09 * std::cos(2.34932647403 + 11506.7697697936 * t); + result += 1e-09 * std::cos(0.85621569847 + 6290.1893969922 * t); + result += 9.2e-10 * std::cos(5.10587476002 + 7079.3738568078 * t); + result += 1.26e-09 * std::cos(2.65428307012 + 88860.0570709867 * t); + result += 1.06e-09 * std::cos(5.85646710022 + 7860.4193924392 * t); + result += 8.4e-10 * std::cos(3.57457554262 + 16730.4636895958 * t); + result += 8.9e-10 * std::cos(4.21433259618 + 83996.8473181119 * t); + result += 9.7e-10 * std::cos(5.57938280855 + 13367.9726311066 * t); + result += 1.02e-09 * std::cos(2.05853060226 + 87.30820453981 * t); + result += 8e-10 * std::cos(4.73792651816 + 11926.2544136688 * t); + result += 8e-10 * std::cos(5.41418965044 + 10973.55568635 * t); + result += 1.06e-09 * std::cos(4.10978997399 + 3496.032826134 * t); + result += 1.02e-09 * std::cos(3.62650006043 + 244287.600007228 * t); + result += 7.5e-10 * std::cos(4.89483161769 + 5643.1785636774 * t); + result += 8.7e-10 * std::cos(0.42863750683 + 11015.1064773348 * t); + result += 6.9e-10 * std::cos(1.8890876072 + 10177.2576795336 * t); + result += 8.9e-10 * std::cos(1.35567273119 + 6681.2248533996 * t); + result += 6.6e-10 * std::cos(0.99455837265 + 6525.8044539654 * t); + result += 6.7e-10 * std::cos(5.5124099707 + 3097.88382272579 * t); + result += 7.6e-10 * std::cos(2.72016814799 + 4164.311989613 * t); + result += 6.3e-10 * std::cos(1.4434990254 + 9917.6968745098 * t); + result += 7.8e-10 * std::cos(3.51469733747 + 11856.2186514245 * t); + result += 8.5e-10 * std::cos(0.50956043858 + 10575.4066829418 * t); + result += 6.7e-10 * std::cos(3.62043033405 + 16496.3613962024 * t); + result += 5.5e-10 * std::cos(5.24637517308 + 3340.6124266998 * t); + result += 4.8e-10 * std::cos(5.43966777314 + 20426.571092422 * t); + result += 6.4e-10 * std::cos(5.79535817813 + 2388.8940204492 * t); + result += 4.6e-10 * std::cos(5.43499966519 + 6275.9623029906 * t); + result += 5e-10 * std::cos(3.86263598617 + 5729.506447149 * t); + result += 4.4e-10 * std::cos(1.52269529228 + 12168.0026965746 * t); + result += 5.7e-10 * std::cos(4.96352373486 + 14945.3161735544 * t); + result += 4.5e-10 * std::cos(1.0086123016 + 8635.9420037632 * t); + result += 4.3e-10 * std::cos(3.30685683359 + 9779.1086761254 * t); + result += 4.2e-10 * std::cos(0.6348125893 + 2699.7348193176 * t); + result += 4.1e-10 * std::cos(5.67996766641 + 11712.9553182308 * t); + result += 5.6e-10 * std::cos(4.34024451468 + 90955.5516944961 * t); + result += 4.1e-10 * std::cos(5.81722212845 + 709.9330485583 * t); + result += 5.3e-10 * std::cos(6.17052087143 + 233141.314404362 * t); + result += 3.7e-10 * std::cos(3.12495025087 + 16200.7727245012 * t); + result += 3.5e-10 * std::cos(5.76973458495 + 12569.6748183318 * t); + result += 3.7e-10 * std::cos(0.31656444326 + 24356.7807886416 * t); + result += 3.5e-10 * std::cos(0.96229051027 + 17298.1823273262 * t); + result += 3.3e-10 * std::cos(5.23130355867 + 5331.3574437408 * t); + result += 3.5e-10 * std::cos(0.62517020593 + 25158.6017197654 * t); + result += 3.5e-10 * std::cos(0.80004512129 + 13916.0191096416 * t); + result += 3.7e-10 * std::cos(2.89336088688 + 12721.572099417 * t); + result += 3e-10 * std::cos(4.50198402401 + 23543.2305046818 * t); + result += 3e-10 * std::cos(5.31355708693 + 18319.5365848796 * t); + result += 2.9e-10 * std::cos(3.47275229977 + 13119.7211028252 * t); + result += 2.9e-10 * std::cos(3.11002782516 + 4136.9104335162 * t); + result += 3.2e-10 * std::cos(5.52273255667 + 5753.3848848968 * t); + result += 3.5e-10 * std::cos(3.7969999668 + 143571.324284816 * t); + result += 2.6e-10 * std::cos(1.50634201907 + 154717.609887683 * t); + result += 3e-10 * std::cos(3.53519084118 + 6284.0561710596 * t); + result += 2.3e-10 * std::cos(4.41808025967 + 5884.9268465832 * t); + result += 2.5e-10 * std::cos(1.38477355808 + 65147.6197681377 * t); + result += 2.3e-10 * std::cos(3.49782549797 + 7477.522860216 * t); + result += 1.9e-10 * std::cos(3.14329413716 + 6496.3749454294 * t); + result += 1.9e-10 * std::cos(2.20135125199 + 18073.7049386502 * t); + result += 1.9e-10 * std::cos(4.95020255309 + 3930.2096962196 * t); + result += 1.9e-10 * std::cos(0.57998702747 + 31415.379249957 * t); + result += 2.1e-10 * std::cos(1.75474323399 + 12139.5535091068 * t); + result += 1.9e-10 * std::cos(3.92233070499 + 19651.048481098 * t); + result += 1.4e-10 * std::cos(0.98131213224 + 12559.038152982 * t); + result += 1.9e-10 * std::cos(4.93309333729 + 2942.4634232916 * t); + result += 1.6e-10 * std::cos(5.55997534558 + 8827.3902698748 * t); + result += 1.3e-10 * std::cos(1.68808165516 + 4535.0594369244 * t); + result += 1.3e-10 * std::cos(0.33982116161 + 4933.2084403326 * t); + result += 1.2e-10 * std::cos(1.85426309994 + 5856.4776591154 * t); + result += 1e-10 * std::cos(4.82763996845 + 13095.8426650774 * t); + result += 1.1e-10 * std::cos(5.38005490571 + 11790.6290886588 * t); + result += 1e-10 * std::cos(1.40815507226 + 10988.808157535 * t); + result += 1.1e-10 * std::cos(3.05005267431 + 17260.1546546904 * t); + result += 1e-10 * std::cos(4.93364992366 + 12352.8526045448 * t); + return result; +} + +constexpr double getEarthL3(double t) +{ + double result = 0.0; + result += 2.89226e-06 * std::cos(5.84384198723 + 6283.0758499914 * t); + result += 3.4955e-07; + result += 1.6819e-07 * std::cos(5.48766912348 + 12566.1516999828 * t); + result += 2.962e-08 * std::cos(5.19577265202 + 155.4203994342 * t); + result += 1.288e-08 * std::cos(4.72200252235 + 3.523118349 * t); + result += 6.35e-09 * std::cos(5.96925937141 + 242.728603974 * t); + result += 7.14e-09 * std::cos(5.30045809128 + 18849.2275499742 * t); + result += 4.02e-09 * std::cos(3.78682982419 + 553.5694028424 * t); + result += 7.2e-10 * std::cos(4.2976812618 + 6286.5989683404 * t); + result += 6.7e-10 * std::cos(0.90721687647 + 6127.6554505572 * t); + result += 3.6e-10 * std::cos(5.24029648014 + 6438.4962494256 * t); + result += 2.4e-10 * std::cos(5.16003960716 + 25132.3033999656 * t); + result += 2.3e-10 * std::cos(3.01921570335 + 6309.3741697912 * t); + result += 1.7e-10 * std::cos(5.82863573502 + 6525.8044539654 * t); + result += 1.7e-10 * std::cos(3.6777286393 + 71430.6956181291 * t); + result += 9e-11 * std::cos(4.58467294499 + 1577.3435424478 * t); + result += 8e-11 * std::cos(1.40626662824 + 11856.2186514245 * t); + result += 8e-11 * std::cos(5.07561257196 + 6256.7775301916 * t); + result += 7e-11 * std::cos(2.82473374405 + 83996.8473181119 * t); + result += 5e-11 * std::cos(2.71488713339 + 10977.078804699 * t); + result += 5e-11 * std::cos(3.76879847273 + 12036.4607348882 * t); + result += 5e-11 * std::cos(4.28412873331 + 6275.9623029906 * t); + return result; +} + +constexpr double getEarthL4(double t) +{ + double result = 0.0; + result -= -1.14084e-06; + result += 7.717e-08 * std::cos(4.13446589358 + 6283.0758499914 * t); + result += 7.65e-09 * std::cos(3.83803776214 + 12566.1516999828 * t); + result += 4.2e-09 * std::cos(0.41925861858 + 155.4203994342 * t); + result += 4e-10 * std::cos(3.5984758584 + 18849.2275499742 * t); + result += 4.1e-10 * std::cos(3.14398414077 + 3.523118349 * t); + result += 3.5e-10 * std::cos(5.00298940826 + 5573.1428014331 * t); + result += 1.3e-10 * std::cos(0.48794833701 + 77713.7714681205 * t); + result += 1e-10 * std::cos(5.6480176635 + 6127.6554505572 * t); + result += 8e-11 * std::cos(2.84160570605 + 161000.685737674 * t); + result += 2e-11 * std::cos(0.54912904658 + 6438.4962494256 * t); + return result; +} + +constexpr double getEarthL5(double t) +{ + double result = 0.0; + result -= -8.78e-09; + result += 1.72e-09 * std::cos(2.7657906951 + 6283.0758499914 * t); + result += 5e-10 * std::cos(2.01353298182 + 155.4203994342 * t); + result += 2.8e-10 * std::cos(2.21496423926 + 12566.1516999828 * t); + result += 5e-11 * std::cos(1.75600058765 + 18849.2275499742 * t); + return result; +} + +constexpr double getEarthB0(double t) +{ + double result = 0.0; + result += 2.7962e-06 * std::cos(3.19870156017 + 84334.6615813083 * t); + result += 1.01643e-06 * std::cos(5.42248619256 + 5507.5532386674 * t); + result += 8.0445e-07 * std::cos(3.88013204458 + 5223.6939198022 * t); + result += 4.3806e-07 * std::cos(3.70444689758 + 2352.8661537718 * t); + result += 3.1933e-07 * std::cos(4.00026369781 + 1577.3435424478 * t); + result += 2.2724e-07 * std::cos(3.9847383156 + 1047.7473117547 * t); + result += 1.6392e-07 * std::cos(3.56456119782 + 5856.4776591154 * t); + result += 1.8141e-07 * std::cos(4.98367470263 + 6283.0758499914 * t); + result += 1.4443e-07 * std::cos(3.70275614914 + 9437.762934887 * t); + result += 1.4304e-07 * std::cos(3.41117857525 + 10213.285546211 * t); + result += 1.1246e-07 * std::cos(4.8282069053 + 14143.4952424306 * t); + result += 1.09e-07 * std::cos(2.08574562327 + 6812.766815086 * t); + result += 9.714e-08 * std::cos(3.47303947752 + 4694.0029547076 * t); + result += 1.0367e-07 * std::cos(4.05663927946 + 71092.8813549327 * t); + result += 8.775e-08 * std::cos(4.44016515669 + 5753.3848848968 * t); + result += 8.366e-08 * std::cos(4.9925151218 + 7084.8967811152 * t); + result += 6.921e-08 * std::cos(4.32559054073 + 6275.9623029906 * t); + result += 9.145e-08 * std::cos(1.14182646613 + 6620.8901131878 * t); + result += 7.194e-08 * std::cos(3.60193205752 + 529.6909650946 * t); + result += 7.698e-08 * std::cos(5.55425745881 + 167621.575850862 * t); + result += 5.285e-08 * std::cos(2.48446991566 + 4705.7323075436 * t); + result += 5.208e-08 * std::cos(6.24992674537 + 18073.7049386502 * t); + result += 4.529e-08 * std::cos(2.33827747356 + 6309.3741697912 * t); + result += 5.579e-08 * std::cos(4.41023653738 + 7860.4193924392 * t); + result += 4.743e-08 * std::cos(0.70995680136 + 5884.9268465832 * t); + result += 4.301e-08 * std::cos(1.10255777773 + 6681.2248533996 * t); + result += 3.849e-08 * std::cos(1.82229412531 + 5486.777843175 * t); + result += 4.093e-08 * std::cos(5.11700141207 + 13367.9726311066 * t); + result += 3.681e-08 * std::cos(0.43793170356 + 3154.6870848956 * t); + result += 3.42e-08 * std::cos(5.42034800952 + 6069.7767545534 * t); + result += 3.617e-08 * std::cos(6.04641937526 + 3930.2096962196 * t); + result += 3.67e-08 * std::cos(4.58210192227 + 12194.0329146209 * t); + result += 2.918e-08 * std::cos(1.95463881126 + 10977.078804699 * t); + result += 2.797e-08 * std::cos(5.61259275048 + 11790.6290886588 * t); + result += 2.502e-08 * std::cos(0.60499729367 + 6496.3749454294 * t); + result += 2.319e-08 * std::cos(5.01648216014 + 1059.3819301892 * t); + result += 2.684e-08 * std::cos(1.39470396488 + 22003.9146348698 * t); + result += 2.428e-08 * std::cos(3.24183056052 + 78051.5857313169 * t); + result += 2.12e-08 * std::cos(4.30691000285 + 5643.1785636774 * t); + result += 2.257e-08 * std::cos(3.15557225618 + 90617.7374312997 * t); + result += 1.813e-08 * std::cos(3.75574218285 + 3340.6124266998 * t); + result += 2.226e-08 * std::cos(2.79699346659 + 12036.4607348882 * t); + result += 1.888e-08 * std::cos(0.86991545823 + 8635.9420037632 * t); + result += 1.517e-08 * std::cos(1.95852055701 + 398.1490034082 * t); + result += 1.581e-08 * std::cos(3.19976230948 + 5088.6288397668 * t); + result += 1.421e-08 * std::cos(6.25530883827 + 2544.3144198834 * t); + result += 1.595e-08 * std::cos(0.25619915135 + 17298.1823273262 * t); + result += 1.391e-08 * std::cos(4.69964175561 + 7058.5984613154 * t); + result += 1.478e-08 * std::cos(2.81808207569 + 25934.1243310894 * t); + result += 1.481e-08 * std::cos(3.65823554806 + 11506.7697697936 * t); + result += 1.693e-08 * std::cos(4.95689385293 + 156475.290247996 * t); + result += 1.183e-08 * std::cos(1.29343061246 + 775.522611324 * t); + result += 1.114e-08 * std::cos(2.37889311846 + 3738.761430108 * t); + result += 9.94e-09 * std::cos(4.30088900425 + 9225.539273283 * t); + result += 9.24e-09 * std::cos(3.06451026812 + 4164.311989613 * t); + result += 8.67e-09 * std::cos(0.55606931068 + 8429.2412664666 * t); + result += 9.88e-09 * std::cos(5.97286104208 + 7079.3738568078 * t); + result += 8.24e-09 * std::cos(1.50984806173 + 10447.3878396044 * t); + result += 9.15e-09 * std::cos(0.12635654592 + 11015.1064773348 * t); + result += 7.42e-09 * std::cos(1.99159139281 + 26087.9031415742 * t); + result -= -1.039e-08; + result += 8.5e-09 * std::cos(4.24120016095 + 29864.334027309 * t); + result += 7.55e-09 * std::cos(2.8963187332 + 4732.0306273434 * t); + result += 7.14e-09 * std::cos(1.37548118603 + 2146.1654164752 * t); + result += 7.08e-09 * std::cos(1.91406542362 + 8031.0922630584 * t); + result += 7.46e-09 * std::cos(0.57893808616 + 796.2980068164 * t); + result += 8.02e-09 * std::cos(5.1233913723 + 2942.4634232916 * t); + result += 7.51e-09 * std::cos(1.67479850166 + 21228.3920235458 * t); + result += 6.02e-09 * std::cos(4.09976538826 + 64809.8055049413 * t); + result += 5.94e-09 * std::cos(3.49580704962 + 16496.3613962024 * t); + result += 5.92e-09 * std::cos(4.59481504319 + 4690.4798363586 * t); + result += 5.3e-09 * std::cos(5.739792952 + 8827.3902698748 * t); + result += 5.03e-09 * std::cos(5.66433137112 + 33794.5437235286 * t); + result += 4.83e-09 * std::cos(1.57106522411 + 801.8209311238 * t); + result += 4.38e-09 * std::cos(0.06707733767 + 3128.3887650958 * t); + result += 4.23e-09 * std::cos(2.86944595927 + 12566.1516999828 * t); + result += 5.04e-09 * std::cos(3.2620766916 + 7632.9432596502 * t); + result += 5.52e-09 * std::cos(1.02926440457 + 239762.204517549 * t); + result += 4.27e-09 * std::cos(3.6743437821 + 213.299095438 * t); + result += 4.04e-09 * std::cos(1.46193297142 + 15720.8387848784 * t); + result += 5.03e-09 * std::cos(4.85802444134 + 6290.1893969922 * t); + result += 4.17e-09 * std::cos(0.81920713533 + 5216.5803728014 * t); + result += 3.65e-09 * std::cos(0.01002966162 + 12168.0026965746 * t); + result += 3.63e-09 * std::cos(1.28376436579 + 6206.8097787158 * t); + result += 3.53e-09 * std::cos(4.7005913311 + 7234.794256242 * t); + result += 4.15e-09 * std::cos(0.96862624175 + 4136.9104335162 * t); + result += 3.87e-09 * std::cos(3.09145061418 + 25158.6017197654 * t); + result += 3.73e-09 * std::cos(2.65119262792 + 7342.4577801806 * t); + result += 3.61e-09 * std::cos(2.97762937739 + 9623.6882766912 * t); + result += 4.18e-09 * std::cos(3.75759994446 + 5230.807466803 * t); + result += 3.96e-09 * std::cos(1.22507712354 + 6438.4962494256 * t); + result += 3.22e-09 * std::cos(1.21162178805 + 8662.240323563 * t); + result += 2.84e-09 * std::cos(5.64170320068 + 1589.0728952838 * t); + result += 3.79e-09 * std::cos(1.72248432748 + 14945.3161735544 * t); + result += 3.2e-09 * std::cos(3.94161159962 + 7330.8231617461 * t); + result += 3.13e-09 * std::cos(5.47602376446 + 1194.4470102246 * t); + result += 2.92e-09 * std::cos(1.38971327603 + 11769.8536931664 * t); + result += 3.05e-09 * std::cos(0.80429352049 + 37724.7534197482 * t); + result += 2.57e-09 * std::cos(5.81382809757 + 426.598190876 * t); + result += 2.65e-09 * std::cos(6.10358507671 + 6836.6452528338 * t); + result += 2.5e-09 * std::cos(4.56452895547 + 7477.522860216 * t); + result += 2.66e-09 * std::cos(2.62926282354 + 7238.6755916 * t); + result += 2.63e-09 * std::cos(6.22089501237 + 6133.5126528568 * t); + result += 3.06e-09 * std::cos(2.79682380531 + 1748.016413067 * t); + result += 2.36e-09 * std::cos(2.46093023714 + 11371.7046897582 * t); + result += 3.16e-09 * std::cos(1.62662805006 + 250908.490120415 * t); + result += 2.16e-09 * std::cos(3.68721275185 + 5849.3641121146 * t); + result += 2.3e-09 * std::cos(0.36165162947 + 5863.5912061162 * t); + result += 2.33e-09 * std::cos(5.03509933858 + 20426.571092422 * t); + result += 2e-09 * std::cos(5.86073159059 + 4535.0594369244 * t); + result += 2.77e-09 * std::cos(4.65400292395 + 82239.1669577989 * t); + result += 2.09e-09 * std::cos(3.72323200804 + 10973.55568635 * t); + result += 1.99e-09 * std::cos(5.05186622555 + 5429.8794682394 * t); + result += 2.56e-09 * std::cos(2.4092327977 + 19651.048481098 * t); + result += 2.1e-09 * std::cos(4.50691909144 + 29088.811415985 * t); + result += 1.81e-09 * std::cos(6.00294783127 + 4292.3308329504 * t); + result += 2.49e-09 * std::cos(0.12900984422 + 154379.795624486 * t); + result += 2.09e-09 * std::cos(3.87759458598 + 17789.845619785 * t); + result += 2.25e-09 * std::cos(3.18339652605 + 18875.525869774 * t); + result += 1.91e-09 * std::cos(4.53897489299 + 18477.1087646123 * t); + result += 1.72e-09 * std::cos(2.09694183014 + 13095.8426650774 * t); + result += 1.82e-09 * std::cos(3.161079435 + 16730.4636895958 * t); + result += 1.88e-09 * std::cos(2.22746128596 + 41654.9631159678 * t); + result += 1.64e-09 * std::cos(5.18686275017 + 5481.2549188676 * t); + result += 1.6e-09 * std::cos(2.49298855159 + 12592.4500197826 * t); + result += 1.55e-09 * std::cos(1.5959543823 + 10021.8372800994 * t); + result += 1.35e-09 * std::cos(0.21349051064 + 10988.808157535 * t); + result += 1.78e-09 * std::cos(3.8037517797 + 23581.2581773176 * t); + result += 1.23e-09 * std::cos(1.66800739151 + 15110.4661198662 * t); + result += 1.22e-09 * std::cos(2.72678272244 + 18849.2275499742 * t); + result += 1.26e-09 * std::cos(1.1767551291 + 14919.0178537546 * t); + result += 1.42e-09 * std::cos(3.95053441332 + 337.8142631964 * t); + result += 1.16e-09 * std::cos(6.06340906229 + 6709.6740408674 * t); + result += 1.37e-09 * std::cos(3.52143246757 + 12139.5535091068 * t); + result += 1.36e-09 * std::cos(2.92179113542 + 32217.2001810808 * t); + result += 1.1e-09 * std::cos(3.51203379263 + 18052.9295431578 * t); + result += 1.47e-09 * std::cos(4.63371971408 + 22805.7355659936 * t); + result += 1.08e-09 * std::cos(5.45280814878 + 7.1135470008 * t); + result += 1.48e-09 * std::cos(0.65447253687 + 95480.9471841745 * t); + result += 1.19e-09 * std::cos(5.92110458985 + 33019.0211122046 * t); + result += 1.1e-09 * std::cos(5.34824206306 + 639.897286314 * t); + result += 1.06e-09 * std::cos(3.71081682629 + 14314.1681130498 * t); + result += 1.39e-09 * std::cos(6.17607198418 + 24356.7807886416 * t); + result += 1.18e-09 * std::cos(5.5973871267 + 161338.50000087 * t); + result += 1.17e-09 * std::cos(3.6506527164 + 45585.1728121874 * t); + result += 1.27e-09 * std::cos(4.74596574209 + 49515.382508407 * t); + result += 1.2e-09 * std::cos(1.04211499785 + 6915.8595893046 * t); + result += 1.2e-09 * std::cos(5.60638811846 + 5650.2921106782 * t); + result += 1.15e-09 * std::cos(3.10668213289 + 14712.317116458 * t); + result += 9.9e-10 * std::cos(0.69018940049 + 12779.4507954208 * t); + result += 9.7e-10 * std::cos(1.07908724794 + 9917.6968745098 * t); + result += 9.3e-10 * std::cos(2.62295197319 + 17260.1546546904 * t); + result += 9.9e-10 * std::cos(4.45774681732 + 4933.2084403326 * t); + result += 1.23e-09 * std::cos(1.37488922089 + 28286.9904848612 * t); + result += 1.21e-09 * std::cos(5.19767249813 + 27511.4678735372 * t); + result += 1.05e-09 * std::cos(0.87192267806 + 77375.9572049241 * t); + result += 8.7e-10 * std::cos(3.9363781295 + 17654.7805397496 * t); + result += 1.22e-09 * std::cos(2.2395606868 + 83997.0911355954 * t); + result += 8.7e-10 * std::cos(4.18201600952 + 22779.4372461938 * t); + result += 1.04e-09 * std::cos(4.59580877295 + 1349.8674096588 * t); + result += 1.02e-09 * std::cos(2.83545248411 + 12352.8526045448 * t); + result += 1.02e-09 * std::cos(3.97386522171 + 10818.1352869158 * t); + result += 1.01e-09 * std::cos(4.32892825857 + 36147.4098773004 * t); + result += 9.4e-10 * std::cos(5.00001709261 + 150192.214398004 * t); + result += 7.7e-10 * std::cos(3.97199369296 + 1592.5960136328 * t); + result += 1e-09 * std::cos(6.07733097102 + 26735.9452622132 * t); + result += 8.6e-10 * std::cos(5.2602963825 + 28313.288804661 * t); + result += 9.3e-10 * std::cos(4.31900620254 + 44809.6502008634 * t); + result += 7.6e-10 * std::cos(6.22743405935 + 13521.7514415914 * t); + result += 7.2e-10 * std::cos(1.55820597747 + 6256.7775301916 * t); + result += 8.2e-10 * std::cos(4.95202664555 + 10575.4066829418 * t); + result += 8.2e-10 * std::cos(1.69647647075 + 1990.745017041 * t); + result += 7.5e-10 * std::cos(2.29836095644 + 3634.6210245184 * t); + result += 7.5e-10 * std::cos(2.66367876557 + 16200.7727245012 * t); + result += 8.7e-10 * std::cos(0.26630214764 + 31441.6775697568 * t); + result += 7.7e-10 * std::cos(2.25530954137 + 5235.3285382367 * t); + result += 7.6e-10 * std::cos(1.09869730846 + 12903.9659631792 * t); + result += 5.8e-10 * std::cos(4.28246138307 + 12559.038152982 * t); + result += 6.4e-10 * std::cos(5.51112830114 + 173904.651700853 * t); + result += 5.6e-10 * std::cos(2.60133794851 + 73188.3759784421 * t); + result += 5.5e-10 * std::cos(5.81483150022 + 143233.51002162 * t); + result += 5.4e-10 * std::cos(3.38482031504 + 323049.118787103 * t); + result += 3.9e-10 * std::cos(3.28500401343 + 71768.5098813255 * t); + result += 3.9e-10 * std::cos(3.1123991069 + 96900.8132812911 * t); + return result; +} + +constexpr double getEarthB1(double t) +{ + double result = 0.0; + result += 9.03e-08 * std::cos(3.8972906189 + 5507.5532386674 * t); + result += 6.177e-08 * std::cos(1.73038850355 + 5223.6939198022 * t); + result += 3.8e-08 * std::cos(5.24404145734 + 2352.8661537718 * t); + result += 2.834e-08 * std::cos(2.4734503745 + 1577.3435424478 * t); + result += 1.817e-08 * std::cos(0.41874743765 + 6283.0758499914 * t); + result += 1.499e-08 * std::cos(1.83320979291 + 5856.4776591154 * t); + result += 1.466e-08 * std::cos(5.69401926017 + 5753.3848848968 * t); + result += 1.301e-08 * std::cos(2.18890066314 + 9437.762934887 * t); + result += 1.233e-08 * std::cos(4.95222451476 + 10213.285546211 * t); + result += 1.021e-08 * std::cos(0.12866660208 + 7860.4193924392 * t); + result += 9.82e-09 * std::cos(0.09005453285 + 14143.4952424306 * t); + result += 8.65e-09 * std::cos(1.73949953555 + 3930.2096962196 * t); + result += 5.81e-09 * std::cos(2.26949174067 + 5884.9268465832 * t); + result += 5.24e-09 * std::cos(5.65662503159 + 529.6909650946 * t); + result += 4.73e-09 * std::cos(6.22750969242 + 6309.3741697912 * t); + result += 4.51e-09 * std::cos(1.53288619213 + 18073.7049386502 * t); + result += 3.64e-09 * std::cos(3.61614477374 + 13367.9726311066 * t); + result += 3.72e-09 * std::cos(3.2247072132 + 6275.9623029906 * t); + result += 2.68e-09 * std::cos(2.34341267879 + 11790.6290886588 * t); + result += 3.22e-09 * std::cos(0.94084045832 + 6069.7767545534 * t); + result += 2.32e-09 * std::cos(0.26781182579 + 7058.5984613154 * t); + result += 2.16e-09 * std::cos(6.05952221329 + 10977.078804699 * t); + result += 2.32e-09 * std::cos(2.93325646109 + 22003.9146348698 * t); + result += 2.04e-09 * std::cos(3.86264841382 + 6496.3749454294 * t); + result += 2.02e-09 * std::cos(2.81892511133 + 15720.8387848784 * t); + result += 1.85e-09 * std::cos(4.93512381859 + 12036.4607348882 * t); + result += 2.2e-09 * std::cos(3.99305643742 + 6812.766815086 * t); + result += 1.66e-09 * std::cos(1.74970002999 + 11506.7697697936 * t); + result += 2.12e-09 * std::cos(1.57166285369 + 4694.0029547076 * t); + result += 1.57e-09 * std::cos(1.08259734788 + 5643.1785636774 * t); + result += 1.54e-09 * std::cos(5.99434678412 + 5486.777843175 * t); + result += 1.44e-09 * std::cos(5.23285656085 + 78051.5857313169 * t); + result += 1.44e-09 * std::cos(1.16454655948 + 90617.7374312997 * t); + result += 1.37e-09 * std::cos(2.67760436027 + 6290.1893969922 * t); + result += 1.8e-09 * std::cos(2.06509026215 + 7084.8967811152 * t); + result += 1.21e-09 * std::cos(5.90212574947 + 9225.539273283 * t); + result += 1.5e-09 * std::cos(2.00175038718 + 5230.807466803 * t); + result += 1.49e-09 * std::cos(5.06157254516 + 17298.1823273262 * t); + result += 1.18e-09 * std::cos(5.39979058038 + 3340.6124266998 * t); + result += 1.61e-09 * std::cos(3.32421999691 + 6283.3196674749 * t); + result += 1.21e-09 * std::cos(4.36722193162 + 19651.048481098 * t); + result += 1.16e-09 * std::cos(5.83462858507 + 4705.7323075436 * t); + result += 1.28e-09 * std::cos(4.35489873365 + 25934.1243310894 * t); + result += 1.43e-09; + result += 1.09e-09 * std::cos(2.52157834166 + 6438.4962494256 * t); + result += 9.9e-10 * std::cos(2.70727488041 + 5216.5803728014 * t); + result += 1.03e-09 * std::cos(0.93782340879 + 8827.3902698748 * t); + result += 8.2e-10 * std::cos(4.2921468039 + 8635.9420037632 * t); + result += 7.9e-10 * std::cos(2.24085737326 + 1059.3819301892 * t); + result += 9.7e-10 * std::cos(5.50959692365 + 29864.334027309 * t); + result += 7.2e-10 * std::cos(0.21891639822 + 21228.3920235458 * t); + result += 7.1e-10 * std::cos(2.86755026812 + 6681.2248533996 * t); + result += 7.4e-10 * std::cos(2.20184828895 + 37724.7534197482 * t); + result += 6.3e-10 * std::cos(4.45586625948 + 7079.3738568078 * t); + result += 6.1e-10 * std::cos(0.63918772258 + 33794.5437235286 * t); + result += 4.7e-10 * std::cos(2.09070235724 + 3128.3887650958 * t); + result += 4.7e-10 * std::cos(3.325438433 + 26087.9031415742 * t); + result += 4.9e-10 * std::cos(1.60680905005 + 6702.5604938666 * t); + result += 5.7e-10 * std::cos(0.11215813438 + 29088.811415985 * t); + result += 5.6e-10 * std::cos(5.47982934911 + 775.522611324 * t); + result += 5e-10 * std::cos(1.89396788463 + 12139.5535091068 * t); + result += 4.7e-10 * std::cos(2.9721490724 + 20426.571092422 * t); + result += 4.1e-10 * std::cos(5.5532939489 + 11015.1064773348 * t); + result += 4.1e-10 * std::cos(5.91861144924 + 23581.2581773176 * t); + result += 4.5e-10 * std::cos(4.95273290181 + 5863.5912061162 * t); + result += 5e-10 * std::cos(3.62740835096 + 41654.9631159678 * t); + result += 3.7e-10 * std::cos(6.09033460601 + 64809.8055049413 * t); + result += 3.7e-10 * std::cos(5.86153655431 + 12566.1516999828 * t); + result += 4.6e-10 * std::cos(1.65798680284 + 25158.6017197654 * t); + result += 3.8e-10 * std::cos(2.00673650251 + 426.598190876 * t); + result += 3.6e-10 * std::cos(6.24373396652 + 6283.14316029419 * t); + result += 3.6e-10 * std::cos(0.40465162918 + 6283.0085396886 * t); + result += 3.2e-10 * std::cos(6.03707103538 + 2942.4634232916 * t); + result += 4.1e-10 * std::cos(4.86809570283 + 1592.5960136328 * t); + result += 2.8e-10 * std::cos(4.38359423735 + 7632.9432596502 * t); + result += 2.8e-10 * std::cos(6.03334294232 + 17789.845619785 * t); + result += 2.6e-10 * std::cos(3.88971333608 + 5331.3574437408 * t); + result += 2.6e-10 * std::cos(5.94932724051 + 16496.3613962024 * t); + result += 3.1e-10 * std::cos(1.44666331503 + 16730.4636895958 * t); + result += 2.6e-10 * std::cos(6.26376705837 + 23543.2305046818 * t); + result += 3.3e-10 * std::cos(0.93797239147 + 213.299095438 * t); + result += 2.6e-10 * std::cos(3.71858432944 + 13095.8426650774 * t); + result += 2.7e-10 * std::cos(0.60565274405 + 10988.808157535 * t); + result += 2.3e-10 * std::cos(4.4438898555 + 18849.2275499742 * t); + result += 2.8e-10 * std::cos(1.53862289477 + 6279.4854213396 * t); + result += 2.8e-10 * std::cos(1.96831814872 + 6286.6662786432 * t); + result += 2.8e-10 * std::cos(5.78094918529 + 15110.4661198662 * t); + result += 2.6e-10 * std::cos(2.48165809843 + 5729.506447149 * t); + result += 2e-10 * std::cos(3.85655029499 + 9623.6882766912 * t); + result += 2.1e-10 * std::cos(5.83006047147 + 7234.794256242 * t); + result += 2.1e-10 * std::cos(0.69628570421 + 398.1490034082 * t); + result += 2.2e-10 * std::cos(5.02222806555 + 6127.6554505572 * t); + result += 2e-10 * std::cos(3.4761126529 + 6148.010769956 * t); + result += 2e-10 * std::cos(0.90769829044 + 5481.2549188676 * t); + result += 2e-10 * std::cos(0.03081589303 + 6418.1409300268 * t); + result += 2e-10 * std::cos(3.74220084927 + 1589.0728952838 * t); + result += 2.1e-10 * std::cos(4.00149269576 + 3154.6870848956 * t); + result += 1.8e-10 * std::cos(1.58348238359 + 2118.7638603784 * t); + result += 1.9e-10 * std::cos(0.85407021371 + 14712.317116458 * t); + return result; +} + +constexpr double getEarthB2(double t) +{ + double result = 0.0; + result += 1.662e-08 * std::cos(1.62703209173 + 84334.6615813083 * t); + result += 4.92e-09 * std::cos(2.41382223971 + 1047.7473117547 * t); + result += 3.44e-09 * std::cos(2.24353004539 + 5507.5532386674 * t); + result += 2.58e-09 * std::cos(6.00906896311 + 5223.6939198022 * t); + result += 1.31e-09 * std::cos(0.9544734524 + 6283.0758499914 * t); + result += 8.6e-10 * std::cos(1.67530247303 + 7860.4193924392 * t); + result += 9e-10 * std::cos(0.97606804452 + 1577.3435424478 * t); + result += 9e-10 * std::cos(0.37899871725 + 2352.8661537718 * t); + result += 8.9e-10 * std::cos(6.25807507963 + 10213.285546211 * t); + result += 7.5e-10 * std::cos(0.84213523741 + 167621.575850862 * t); + result += 5.2e-10 * std::cos(1.70501566089 + 14143.4952424306 * t); + result += 5.7e-10 * std::cos(6.15295833679 + 12194.0329146209 * t); + result += 5.1e-10 * std::cos(1.2761601674 + 5753.3848848968 * t); + result += 5.1e-10 * std::cos(5.37229738682 + 6812.766815086 * t); + result += 3.4e-10 * std::cos(1.73672994279 + 7058.5984613154 * t); + result += 3.8e-10 * std::cos(2.77761031485 + 10988.808157535 * t); + result += 4.6e-10 * std::cos(3.38617099014 + 156475.290247996 * t); + result += 2.1e-10 * std::cos(1.95248349228 + 8827.3902698748 * t); + result += 1.8e-10 * std::cos(3.33419222028 + 8429.2412664666 * t); + result += 1.9e-10 * std::cos(4.32945160287 + 17789.845619785 * t); + result += 1.7e-10 * std::cos(0.66191210656 + 6283.0085396886 * t); + result += 1.8e-10 * std::cos(3.74885333072 + 11769.8536931664 * t); + result += 1.7e-10 * std::cos(4.23058370776 + 10977.078804699 * t); + result += 1.7e-10 * std::cos(1.78116162721 + 5486.777843175 * t); + result += 2.1e-10 * std::cos(1.36972913918 + 12036.4607348882 * t); + result += 1.7e-10 * std::cos(2.79601092529 + 796.2980068164 * t); + result += 1.5e-10 * std::cos(0.4308784885 + 11790.6290886588 * t); + result += 1.7e-10 * std::cos(1.35132152761 + 78051.5857313169 * t); + result += 1.5e-10 * std::cos(1.17032155085 + 213.299095438 * t); + result += 1.8e-10 * std::cos(2.85221514199 + 5088.6288397668 * t); + result += 1.7e-10 * std::cos(0.21780913672 + 6283.14316029419 * t); + result += 1.3e-10 * std::cos(1.21201504386 + 25132.3033999656 * t); + result += 1.2e-10 * std::cos(1.12953712197 + 90617.7374312997 * t); + result += 1.2e-10 * std::cos(5.13714452592 + 7079.3738568078 * t); + result += 1.3e-10 * std::cos(3.79842135217 + 4933.2084403326 * t); + result += 1.2e-10 * std::cos(4.89407978213 + 3738.761430108 * t); + result += 1.5e-10 * std::cos(6.05682328852 + 398.1490034082 * t); + result += 1.4e-10 * std::cos(4.81029291856 + 4694.0029547076 * t); + result += 1.1e-10 * std::cos(0.61684523405 + 3128.3887650958 * t); + result += 1.1e-10 * std::cos(5.328765385 + 6040.3472460174 * t); + result += 1.4e-10 * std::cos(5.27227350286 + 4535.0594369244 * t); + result += 1.1e-10 * std::cos(2.39292099451 + 5331.3574437408 * t); + result += 1e-10 * std::cos(4.4529653271 + 6525.8044539654 * t); + result += 1.4e-10 * std::cos(4.66400985037 + 8031.0922630584 * t); + result += 1e-10 * std::cos(3.22472385926 + 9437.762934887 * t); + result += 1.1e-10 * std::cos(3.80913404437 + 801.8209311238 * t); + result += 1e-10 * std::cos(5.15032130575 + 11371.7046897582 * t); + result += 1.3e-10 * std::cos(0.98720797401 + 5729.506447149 * t); + result += 9e-11 * std::cos(5.94191743597 + 7632.9432596502 * t); + return result; +} + +constexpr double getEarthB3(double t) +{ + double result = 0.0; + result += 1.1e-10 * std::cos(0.23877262399 + 7860.4193924392 * t); + result += 9e-11 * std::cos(1.16069982609 + 5507.5532386674 * t); + result += 8e-11 * std::cos(1.65357552925 + 5884.9268465832 * t); + result += 8e-11 * std::cos(2.86720038197 + 7058.5984613154 * t); + result += 7e-11 * std::cos(3.04818741666 + 5486.777843175 * t); + result += 7e-11 * std::cos(2.59437103785 + 529.6909650946 * t); + result += 8e-11 * std::cos(4.02863090524 + 6256.7775301916 * t); + result += 8e-11 * std::cos(2.42003508927 + 5753.3848848968 * t); + result += 6e-11 * std::cos(0.84181087594 + 6275.9623029906 * t); + result += 6e-11 * std::cos(5.40160929468 + 1577.3435424478 * t); + result += 7e-11 * std::cos(2.73399865247 + 6309.3741697912 * t); + return result; +} + +constexpr double getEarthB4(double t) +{ + double result = 0.0; + result += 4e-11 * std::cos(0.79662198849 + 6438.4962494256 * t); + result += 5e-11 * std::cos(0.84308705203 + 1047.7473117547 * t); + result += 5e-11 * std::cos(0.05711572303 + 84334.6615813083 * t); + result += 3e-11 * std::cos(3.46779895686 + 6279.5527316424 * t); + result += 3e-11 * std::cos(2.89822201212 + 6127.6554505572 * t); + return result; +} + +constexpr double getEarthR0(double t) +{ + double result = 0.0; + result += 1.00013988799; + result += 0.01670699626 * std::cos(3.09846350771 + 6283.0758499914 * t); + result += 0.00013956023 * std::cos(3.0552460962 + 12566.1516999828 * t); + result += 3.08372e-05 * std::cos(5.19846674381 + 77713.7714681205 * t); + result += 1.628461e-05 * std::cos(1.17387749012 + 5753.3848848968 * t); + result += 1.575568e-05 * std::cos(2.84685245825 + 7860.4193924392 * t); + result += 9.24799e-06 * std::cos(5.45292234084 + 11506.7697697936 * t); + result += 5.42444e-06 * std::cos(4.56409149777 + 3930.2096962196 * t); + result += 4.7211e-06 * std::cos(3.66100022149 + 5884.9268465832 * t); + result += 3.2878e-06 * std::cos(5.89983646482 + 5223.6939198022 * t); + result += 3.45983e-06 * std::cos(0.96368617687 + 5507.5532386674 * t); + result += 3.06784e-06 * std::cos(0.29867139512 + 5573.1428014331 * t); + result += 1.74844e-06 * std::cos(3.01193636534 + 18849.2275499742 * t); + result += 2.43189e-06 * std::cos(4.27349536153 + 11790.6290886588 * t); + result += 2.11829e-06 * std::cos(5.84714540314 + 1577.3435424478 * t); + result += 1.85752e-06 * std::cos(5.02194447178 + 10977.078804699 * t); + result += 1.09835e-06 * std::cos(5.05510636285 + 5486.777843175 * t); + result += 9.8316e-07 * std::cos(0.88681311277 + 6069.7767545534 * t); + result += 8.6499e-07 * std::cos(5.68959778254 + 15720.8387848784 * t); + result += 8.5825e-07 * std::cos(1.27083733351 + 161000.685737674 * t); + result += 6.2916e-07 * std::cos(0.92177108832 + 529.6909650946 * t); + result += 5.7056e-07 * std::cos(2.01374292014 + 83996.8473181119 * t); + result += 6.4903e-07 * std::cos(0.27250613787 + 17260.1546546904 * t); + result += 4.9384e-07 * std::cos(3.24501240359 + 2544.3144198834 * t); + result += 5.5736e-07 * std::cos(5.24159798933 + 71430.6956181291 * t); + result += 4.2515e-07 * std::cos(6.01110242003 + 6275.9623029906 * t); + result += 4.6963e-07 * std::cos(2.57805070386 + 775.522611324 * t); + result += 3.8968e-07 * std::cos(5.36071738169 + 4694.0029547076 * t); + result += 4.4661e-07 * std::cos(5.53715807302 + 9437.762934887 * t); + result += 3.566e-07 * std::cos(1.67468058995 + 12036.4607348882 * t); + result += 3.1921e-07 * std::cos(0.18368229781 + 5088.6288397668 * t); + result += 3.1846e-07 * std::cos(1.77775642085 + 398.1490034082 * t); + result += 3.3193e-07 * std::cos(0.24370300098 + 7084.8967811152 * t); + result += 3.8245e-07 * std::cos(2.39255343974 + 8827.3902698748 * t); + result += 2.8464e-07 * std::cos(1.21344868176 + 6286.5989683404 * t); + result += 3.749e-07 * std::cos(0.82952922332 + 19651.048481098 * t); + result += 3.6957e-07 * std::cos(4.90107591914 + 12139.5535091068 * t); + result += 3.4537e-07 * std::cos(1.84270693282 + 2942.4634232916 * t); + result += 2.6275e-07 * std::cos(4.58896850401 + 10447.3878396044 * t); + result += 2.4596e-07 * std::cos(3.78660875483 + 8429.2412664666 * t); + result += 2.3587e-07 * std::cos(0.26866117066 + 796.2980068164 * t); + result += 2.7793e-07 * std::cos(1.89934330904 + 6279.5527316424 * t); + result += 2.3927e-07 * std::cos(4.99598548138 + 5856.4776591154 * t); + result += 2.0349e-07 * std::cos(4.65267995431 + 2146.1654164752 * t); + result += 2.3287e-07 * std::cos(2.80783650928 + 14143.4952424306 * t); + result += 2.2103e-07 * std::cos(1.95004702988 + 3154.6870848956 * t); + result += 1.9506e-07 * std::cos(5.38227371393 + 2352.8661537718 * t); + result += 1.7958e-07 * std::cos(0.19871379385 + 6812.766815086 * t); + result += 1.7174e-07 * std::cos(4.43315560735 + 10213.285546211 * t); + result += 1.619e-07 * std::cos(5.23160507859 + 17789.845619785 * t); + result += 1.7314e-07 * std::cos(6.15200787916 + 16730.4636895958 * t); + result += 1.3814e-07 * std::cos(5.18962074032 + 8031.0922630584 * t); + result += 1.8833e-07 * std::cos(0.67306674027 + 149854.400134808 * t); + result += 1.8331e-07 * std::cos(2.25348733734 + 23581.2581773176 * t); + result += 1.3641e-07 * std::cos(3.68516118804 + 4705.7323075436 * t); + result += 1.3139e-07 * std::cos(0.65289581324 + 13367.9726311066 * t); + result += 1.0414e-07 * std::cos(4.33285688538 + 11769.8536931664 * t); + result += 9.978e-08 * std::cos(4.20126336355 + 6309.3741697912 * t); + result += 1.0169e-07 * std::cos(1.59390681369 + 4690.4798363586 * t); + result += 7.564e-08 * std::cos(2.6256059739 + 6256.7775301916 * t); + result += 9.661e-08 * std::cos(3.6758679122 + 27511.4678735372 * t); + result += 6.743e-08 * std::cos(0.56270332741 + 3340.6124266998 * t); + result += 8.743e-08 * std::cos(6.06359123461 + 1748.016413067 * t); + result += 7.786e-08 * std::cos(3.67371235637 + 12168.0026965746 * t); + result += 6.633e-08 * std::cos(5.66149277792 + 11371.7046897582 * t); + result += 7.712e-08 * std::cos(0.31242577789 + 7632.9432596502 * t); + result += 6.592e-08 * std::cos(3.13576266188 + 801.8209311238 * t); + result += 7.46e-08 * std::cos(5.64757188143 + 11926.2544136688 * t); + result += 6.933e-08 * std::cos(2.923845864 + 6681.2248533996 * t); + result += 6.802e-08 * std::cos(1.4232980642 + 23013.5395395872 * t); + result += 6.115e-08 * std::cos(5.13393615454 + 1194.4470102246 * t); + result += 6.477e-08 * std::cos(2.64986648492 + 19804.8272915828 * t); + result += 5.233e-08 * std::cos(4.62434053374 + 6438.4962494256 * t); + result += 6.147e-08 * std::cos(3.02863936662 + 233141.314404362 * t); + result += 4.608e-08 * std::cos(1.72194702724 + 7234.794256242 * t); + result += 4.221e-08 * std::cos(1.55697533729 + 7238.6755916 * t); + result += 5.314e-08 * std::cos(2.40716580847 + 11499.6562227928 * t); + result += 5.128e-08 * std::cos(5.3239896569 + 11513.8833167944 * t); + result += 4.77e-08 * std::cos(0.25554312006 + 11856.2186514245 * t); + result += 5.519e-08 * std::cos(2.09089154502 + 17298.1823273262 * t); + result += 5.625e-08 * std::cos(4.34052903053 + 90955.5516944961 * t); + result += 4.578e-08 * std::cos(4.4656964157 + 5746.271337896 * t); + result += 3.788e-08 * std::cos(4.9072938351 + 4164.311989613 * t); + result += 5.337e-08 * std::cos(5.09957905104 + 31441.6775697568 * t); + result += 3.967e-08 * std::cos(1.20054555174 + 1349.8674096588 * t); + result += 4.008e-08 * std::cos(3.03007204392 + 1059.3819301892 * t); + result += 3.476e-08 * std::cos(0.7608027703 + 10973.55568635 * t); + result += 4.232e-08 * std::cos(1.05485713117 + 5760.4984318976 * t); + result += 4.582e-08 * std::cos(3.76570026763 + 6386.16862421 * t); + result += 3.335e-08 * std::cos(3.13829943354 + 6836.6452528338 * t); + result += 3.418e-08 * std::cos(3.00072390334 + 4292.3308329504 * t); + result += 3.598e-08 * std::cos(5.70718084323 + 5643.1785636774 * t); + result += 3.237e-08 * std::cos(4.16448773994 + 9917.6968745098 * t); + result += 4.154e-08 * std::cos(2.59941292162 + 7058.5984613154 * t); + result += 3.362e-08 * std::cos(4.54577697964 + 4732.0306273434 * t); + result += 2.978e-08 * std::cos(1.3056126882 + 6283.14316029419 * t); + result += 2.765e-08 * std::cos(0.51311975679 + 26.2983197998 * t); + result += 2.802e-08 * std::cos(5.66263240521 + 8635.9420037632 * t); + result += 2.927e-08 * std::cos(5.73787481548 + 16200.7727245012 * t); + result += 3.164e-08 * std::cos(1.69140262657 + 11015.1064773348 * t); + result += 2.598e-08 * std::cos(2.96244118586 + 25132.3033999656 * t); + result += 3.519e-08 * std::cos(3.62639325753 + 244287.600007228 * t); + result += 2.676e-08 * std::cos(4.2072570085 + 18073.7049386502 * t); + result += 2.978e-08 * std::cos(1.74971565805 + 6283.0085396886 * t); + result += 2.287e-08 * std::cos(1.06975704977 + 14314.1681130498 * t); + result += 2.863e-08 * std::cos(5.92838131397 + 14712.317116458 * t); + result += 3.071e-08 * std::cos(0.23793217002 + 35371.8872659764 * t); + result += 2.656e-08 * std::cos(0.8995930178 + 12352.8526045448 * t); + result += 2.415e-08 * std::cos(2.79975176257 + 709.9330485583 * t); + result += 2.814e-08 * std::cos(3.51488206882 + 21228.3920235458 * t); + result += 1.977e-08 * std::cos(2.6135829755 + 951.7184062506 * t); + result += 2.548e-08 * std::cos(2.47684686575 + 6208.2942514241 * t); + result += 1.999e-08 * std::cos(0.5609038816 + 7079.3738568078 * t); + result += 2.305e-08 * std::cos(1.05376461628 + 22483.8485744926 * t); + result += 1.855e-08 * std::cos(2.86090681163 + 5216.5803728014 * t); + result += 2.157e-08 * std::cos(1.31396741861 + 154717.609887683 * t); + result += 1.97e-08 * std::cos(4.36929875289 + 167283.761587666 * t); + result += 1.635e-08 * std::cos(5.85571606764 + 10984.1923516998 * t); + result += 1.754e-08 * std::cos(2.14452408833 + 6290.1893969922 * t); + result += 2.154e-08 * std::cos(6.03828341543 + 10873.9860304804 * t); + result += 1.714e-08 * std::cos(3.70157691113 + 1592.5960136328 * t); + result += 1.541e-08 * std::cos(6.21598380732 + 23543.2305046818 * t); + result += 1.611e-08 * std::cos(1.99824499377 + 10969.9652576982 * t); + result += 1.712e-08 * std::cos(1.34295663542 + 3128.3887650958 * t); + result += 1.642e-08 * std::cos(5.55026665339 + 6496.3749454294 * t); + result += 1.502e-08 * std::cos(5.43948825854 + 155.4203994342 * t); + result += 1.827e-08 * std::cos(5.91227480261 + 3738.761430108 * t); + result += 1.726e-08 * std::cos(2.16764983583 + 10575.4066829418 * t); + result += 1.532e-08 * std::cos(5.3568310707 + 13521.7514415914 * t); + result += 1.829e-08 * std::cos(1.66006148731 + 39302.096962196 * t); + result += 1.605e-08 * std::cos(1.90928637633 + 6133.5126528568 * t); + result += 1.282e-08 * std::cos(2.46014880418 + 13916.0191096416 * t); + result += 1.211e-08 * std::cos(4.4136063155 + 3894.1818295422 * t); + result += 1.394e-08 * std::cos(1.77801929354 + 9225.539273283 * t); + result += 1.571e-08 * std::cos(4.95512957592 + 25158.6017197654 * t); + result += 1.205e-08 * std::cos(1.19212540615 + 3.523118349 * t); + result += 1.132e-08 * std::cos(2.69830084955 + 6040.3472460174 * t); + result += 1.504e-08 * std::cos(5.77002730341 + 18209.3302636602 * t); + result += 1.393e-08 * std::cos(1.62621805428 + 5120.6011455836 * t); + result += 1.077e-08 * std::cos(2.93931554233 + 17256.6315363414 * t); + result += 1.232e-08 * std::cos(0.71655165307 + 143571.324284816 * t); + result += 1.087e-08 * std::cos(0.99769687939 + 955.5997416086 * t); + result += 1.068e-08 * std::cos(5.28472576231 + 65147.6197681377 * t); + result += 9.8e-09 * std::cos(5.10949204607 + 6172.869528772 * t); + result += 1.169e-08 * std::cos(3.11664290862 + 14945.3161735544 * t); + result += 1.202e-08 * std::cos(4.02992510402 + 553.5694028424 * t); + result += 9.79e-09 * std::cos(2.00000879212 + 15110.4661198662 * t); + result += 9.62e-09 * std::cos(4.023807714 + 6282.0955289232 * t); + result += 9.99e-09 * std::cos(3.6264300279 + 6262.300454499 * t); + result += 1.03e-08 * std::cos(5.84989900289 + 213.299095438 * t); + result += 1.014e-08 * std::cos(2.84221578218 + 8662.240323563 * t); + result += 1.185e-08 * std::cos(1.51330541132 + 17654.7805397496 * t); + result += 9.67e-09 * std::cos(2.67081017562 + 5650.2921106782 * t); + result += 1.222e-08 * std::cos(2.65423784904 + 88860.0570709867 * t); + result += 9.81e-09 * std::cos(2.36370360283 + 6206.8097787158 * t); + result += 1.033e-08 * std::cos(0.13874927606 + 11712.9553182308 * t); + result += 1.103e-08 * std::cos(3.08477302937 + 43232.3066584156 * t); + result += 7.81e-09 * std::cos(2.53372735932 + 16496.3613962024 * t); + result += 1.019e-08 * std::cos(3.04569392376 + 6037.244203762 * t); + result += 7.95e-09 * std::cos(5.80662989111 + 5230.807466803 * t); + result += 8.13e-09 * std::cos(3.57710279439 + 10177.2576795336 * t); + result += 9.62e-09 * std::cos(5.31470594766 + 6284.0561710596 * t); + result += 7.21e-09 * std::cos(5.96264301567 + 12559.038152982 * t); + result += 9.66e-09 * std::cos(2.74714939953 + 6244.9428143536 * t); + result += 9.21e-09 * std::cos(0.10155275926 + 29088.811415985 * t); + result += 6.92e-09 * std::cos(3.89764447548 + 1589.0728952838 * t); + result += 7.19e-09 * std::cos(5.91791450402 + 4136.9104335162 * t); + result += 7.72e-09 * std::cos(4.05505682353 + 6127.6554505572 * t); + result += 7.12e-09 * std::cos(5.49291532439 + 22003.9146348698 * t); + result += 6.72e-09 * std::cos(1.60700490811 + 11087.2851259184 * t); + result += 6.9e-09 * std::cos(4.50539825563 + 426.598190876 * t); + result += 8.54e-09 * std::cos(3.26104981596 + 20426.571092422 * t); + result += 6.56e-09 * std::cos(4.3241018294 + 16858.4825329332 * t); + result += 8.4e-09 * std::cos(2.59572585222 + 28766.924424484 * t); + result += 6.92e-09 * std::cos(0.61650089011 + 11403.676995575 * t); + result += 7e-09 * std::cos(3.40901167143 + 7.1135470008 * t); + result += 7.26e-09 * std::cos(0.04243053594 + 5481.2549188676 * t); + result += 5.57e-09 * std::cos(4.78317696534 + 20199.094959633 * t); + result += 6.49e-09 * std::cos(1.04027912958 + 6062.6632075526 * t); + result += 6.33e-09 * std::cos(5.70229959167 + 45892.730433157 * t); + result += 5.92e-09 * std::cos(6.11836729658 + 9623.6882766912 * t); + result += 5.23e-09 * std::cos(3.62840021266 + 5333.9002410216 * t); + result += 6.04e-09 * std::cos(5.57734696185 + 10344.2950653858 * t); + result += 4.96e-09 * std::cos(2.21023499449 + 1990.745017041 * t); + result += 6.91e-09 * std::cos(1.96071732602 + 12416.5885028482 * t); + result += 6.4e-09 * std::cos(1.59074172032 + 18319.5365848796 * t); + result += 6.25e-09 * std::cos(3.82362791378 + 13517.8701062334 * t); + result += 6.63e-09 * std::cos(5.08444996779 + 283.8593188652 * t); + result += 4.75e-09 * std::cos(1.17025894287 + 12569.6748183318 * t); + result += 6.64e-09 * std::cos(4.50029469969 + 47162.5163546352 * t); + result += 5.69e-09 * std::cos(0.16310365162 + 17267.2682016912 * t); + result += 5.68e-09 * std::cos(3.86100969474 + 6076.8903015542 * t); + result += 5.39e-09 * std::cos(4.83282276086 + 18422.6293590982 * t); + result += 4.66e-09 * std::cos(0.75872342878 + 7342.4577801806 * t); + result += 5.41e-09 * std::cos(3.07212190507 + 226858.23855437 * t); + result += 4.58e-09 * std::cos(0.26774483096 + 4590.910180489 * t); + result += 6.1e-09 * std::cos(1.53597051291 + 33019.0211122046 * t); + result += 6.17e-09 * std::cos(2.62356328726 + 11190.377900137 * t); + result += 5.48e-09 * std::cos(4.55798855791 + 18875.525869774 * t); + result += 6.33e-09 * std::cos(4.60110281228 + 66567.4858652543 * t); + result += 5.96e-09 * std::cos(5.78202396722 + 632.7837393132 * t); + result += 5.33e-09 * std::cos(5.01786882904 + 12132.439962106 * t); + result += 6.03e-09 * std::cos(5.38458554802 + 316428.228673915 * t); + result += 4.69e-09 * std::cos(0.59168241917 + 21954.157609398 * t); + result += 5.48e-09 * std::cos(3.50613163558 + 17253.0411076896 * t); + result += 5.02e-09 * std::cos(0.98804327589 + 11609.8625440122 * t); + result += 5.68e-09 * std::cos(1.98497313089 + 7668.6374249425 * t); + result += 4.82e-09 * std::cos(1.62141803864 + 12146.6670561076 * t); + result += 3.91e-09 * std::cos(3.68718382989 + 18052.9295431578 * t); + result += 4.57e-09 * std::cos(3.7720573734 + 156137.475984799 * t); + result += 4.01e-09 * std::cos(5.28260651958 + 15671.0817594066 * t); + result += 4.69e-09 * std::cos(1.80963184268 + 12562.6285816338 * t); + result += 5.08e-09 * std::cos(3.36399024699 + 20597.2439630412 * t); + result += 4.5e-09 * std::cos(5.6605429925 + 10454.5013866052 * t); + result += 3.75e-09 * std::cos(4.98534633105 + 9779.1086761254 * t); + result += 5.23e-09 * std::cos(0.97215560834 + 155427.542936241 * t); + result += 4.03e-09 * std::cos(5.13939866506 + 1551.045222648 * t); + result += 3.72e-09 * std::cos(3.69883738807 + 9388.0059094152 * t); + result += 3.67e-09 * std::cos(4.43875659716 + 4535.0594369244 * t); + result += 4.06e-09 * std::cos(4.208631566 + 12592.4500197826 * t); + result += 3.6e-09 * std::cos(2.53924644657 + 242.728603974 * t); + result += 4.71e-09 * std::cos(4.61907324819 + 5436.9930152402 * t); + result += 4.41e-09 * std::cos(5.83872966262 + 3496.032826134 * t); + result += 3.85e-09 * std::cos(4.94496680973 + 24356.7807886416 * t); + result += 3.49e-09 * std::cos(6.15018231784 + 19800.9459562248 * t); + result += 3.55e-09 * std::cos(0.21895678106 + 5429.8794682394 * t); + result += 3.44e-09 * std::cos(5.62993724928 + 2379.1644735716 * t); + result += 3.8e-09 * std::cos(2.72105213143 + 11933.3679606696 * t); + result += 4.32e-09 * std::cos(0.24221790536 + 17996.0311682222 * t); + result += 3.78e-09 * std::cos(5.22517556974 + 7477.522860216 * t); + result += 3.37e-09 * std::cos(5.10888041439 + 5849.3641121146 * t); + result += 3.15e-09 * std::cos(0.57827745123 + 10557.5941608238 * t); + result += 3.18e-09 * std::cos(4.49953141399 + 3634.6210245184 * t); + result += 3.23e-09 * std::cos(1.54274281393 + 10440.2742926036 * t); + result += 3.09e-09 * std::cos(5.76839284397 + 20.7753954924 * t); + result += 3.01e-09 * std::cos(2.34727604008 + 4686.8894077068 * t); + result += 4.14e-09 * std::cos(5.9323760231 + 51092.7260508548 * t); + result += 3.61e-09 * std::cos(2.1639860955 + 28237.2334593894 * t); + result += 2.88e-09 * std::cos(0.18376252189 + 13095.8426650774 * t); + result += 2.77e-09 * std::cos(5.12952205045 + 13119.7211028252 * t); + result += 3.27e-09 * std::cos(6.19222146204 + 6268.8487559898 * t); + result += 2.73e-09 * std::cos(0.30522428863 + 23141.5583829246 * t); + result += 2.67e-09 * std::cos(5.76152585786 + 5966.6839803348 * t); + result += 3.08e-09 * std::cos(5.99280509979 + 22805.7355659936 * t); + result += 3.45e-09 * std::cos(2.92489919444 + 36949.2308084242 * t); + result += 2.53e-09 * std::cos(5.20995219509 + 24072.9214697764 * t); + result += 3.42e-09 * std::cos(5.72702586209 + 16460.333529525 * t); + result += 2.61e-09 * std::cos(2.00304796059 + 6148.010769956 * t); + result += 2.38e-09 * std::cos(5.08264392839 + 6915.8595893046 * t); + result += 2.49e-09 * std::cos(2.94762789744 + 135.0650800354 * t); + result += 3.06e-09 * std::cos(3.89764686987 + 10988.808157535 * t); + result += 3.05e-09 * std::cos(0.05827812117 + 4701.1165017084 * t); + result += 3.19e-09 * std::cos(2.95712862064 + 163096.180361183 * t); + result += 2.09e-09 * std::cos(4.43768461442 + 6546.1597733642 * t); + result += 2.7e-09 * std::cos(2.06643178717 + 4804.209275927 * t); + result += 2.17e-09 * std::cos(0.73691592312 + 6303.8512454838 * t); + result += 2.06e-09 * std::cos(0.32075959415 + 25934.1243310894 * t); + result += 2.18e-09 * std::cos(0.18428135264 + 28286.9904848612 * t); + result += 2.05e-09 * std::cos(5.21312087405 + 20995.3929664494 * t); + result += 1.99e-09 * std::cos(0.44384292491 + 16737.5772365966 * t); + result += 2.3e-09 * std::cos(6.06567392849 + 6287.0080032545 * t); + result += 2.19e-09 * std::cos(1.291942163 + 5326.7866940208 * t); + result += 2.01e-09 * std::cos(1.74700937253 + 22743.4093795164 * t); + result += 2.07e-09 * std::cos(4.45440927276 + 6279.4854213396 * t); + result += 2.69e-09 * std::cos(6.0564044503 + 64471.9912417449 * t); + result += 1.9e-09 * std::cos(0.99256176518 + 29296.6153895786 * t); + result += 2.38e-09 * std::cos(5.42471431221 + 39609.6545831656 * t); + result += 2.62e-09 * std::cos(5.26961924198 + 522.5774180938 * t); + result += 2.1e-09 * std::cos(4.68618183158 + 6254.6266625236 * t); + result += 1.97e-09 * std::cos(2.8062455408 + 4933.2084403326 * t); + result += 2.52e-09 * std::cos(4.36220154608 + 40879.4405046438 * t); + result += 2.61e-09 * std::cos(1.07241516738 + 55022.9357470744 * t); + result += 1.89e-09 * std::cos(3.82966734476 + 419.4846438752 * t); + result += 1.85e-09 * std::cos(4.14324541379 + 5642.1982426092 * t); + result += 2.47e-09 * std::cos(3.44855612987 + 6702.5604938666 * t); + result += 2.05e-09 * std::cos(4.04424043223 + 536.8045120954 * t); + result += 1.91e-09 * std::cos(3.14082686083 + 16723.350142595 * t); + result += 2.22e-09 * std::cos(5.16263907319 + 23539.7073863328 * t); + result += 1.8e-09 * std::cos(4.56214752149 + 6489.2613984286 * t); + result += 2.19e-09 * std::cos(0.80382553358 + 16627.3709153772 * t); + result += 2.27e-09 * std::cos(0.60156339452 + 5905.7022420756 * t); + result += 1.68e-09 * std::cos(0.88753528161 + 16062.1845261168 * t); + result += 1.58e-09 * std::cos(0.92127725775 + 23937.856389741 * t); + result += 1.57e-09 * std::cos(4.69607868164 + 6805.6532680852 * t); + result += 2.07e-09 * std::cos(4.88410451334 + 6286.6662786432 * t); + result += 1.6e-09 * std::cos(4.95943826846 + 10021.8372800994 * t); + result += 1.66e-09 * std::cos(0.97126433565 + 3097.88382272579 * t); + result += 2.09e-09 * std::cos(5.75663411805 + 3646.3503773544 * t); + result += 1.75e-09 * std::cos(6.12762824412 + 239424.390254353 * t); + result += 1.73e-09 * std::cos(3.13887234973 + 6179.9830757728 * t); + result += 1.57e-09 * std::cos(3.62822058179 + 18451.078546566 * t); + result += 1.57e-09 * std::cos(4.67695912235 + 6709.6740408674 * t); + result += 1.46e-09 * std::cos(3.09506069735 + 4907.3020501456 * t); + result += 1.65e-09 * std::cos(2.2713912876 + 10660.6869350424 * t); + result += 2.01e-09 * std::cos(1.67701267433 + 2107.0345075424 * t); + result += 1.44e-09 * std::cos(3.96947747592 + 6019.9919266186 * t); + result += 1.71e-09 * std::cos(5.91302216729 + 6058.7310542895 * t); + result += 1.44e-09 * std::cos(2.1315565512 + 26084.0218062162 * t); + result += 1.51e-09 * std::cos(0.67417383554 + 2388.8940204492 * t); + result += 1.89e-09 * std::cos(5.07122281033 + 263.0839233728 * t); + result += 1.46e-09 * std::cos(5.10373877968 + 10770.8932562618 * t); + result += 1.87e-09 * std::cos(1.23915444627 + 19402.7969528166 * t); + result += 1.74e-09 * std::cos(0.08407293391 + 9380.9596727172 * t); + result += 1.37e-09 * std::cos(1.26247412309 + 12566.2190102856 * t); + result += 1.37e-09 * std::cos(3.52826010842 + 639.897286314 * t); + result += 1.48e-09 * std::cos(1.76124372592 + 5888.4499649322 * t); + result += 1.64e-09 * std::cos(2.39195095081 + 6357.8574485587 * t); + result += 1.46e-09 * std::cos(2.43675816553 + 5881.4037282342 * t); + result += 1.61e-09 * std::cos(1.15721259372 + 26735.9452622132 * t); + result += 1.31e-09 * std::cos(2.51859277344 + 6599.467719648 * t); + result += 1.53e-09 * std::cos(5.85203687779 + 6281.5913772831 * t); + result += 1.51e-09 * std::cos(3.72338532649 + 12669.2444742014 * t); + result += 1.32e-09 * std::cos(2.38417741883 + 6525.8044539654 * t); + result += 1.29e-09 * std::cos(0.75556744143 + 5017.508371365 * t); + result += 1.27e-09 * std::cos(0.00254936441 + 10027.9031957292 * t); + result += 1.48e-09 * std::cos(2.85102145528 + 6418.1409300268 * t); + result += 1.43e-09 * std::cos(5.74460279367 + 26087.9031415742 * t); + result += 1.72e-09 * std::cos(0.4128996224 + 174242.46596405 * t); + result += 1.36e-09 * std::cos(4.15497742275 + 6311.5250374592 * t); + result += 1.7e-09 * std::cos(5.98194913129 + 327574.514276781 * t); + result += 1.24e-09 * std::cos(1.65497607604 + 32217.2001810808 * t); + result += 1.36e-09 * std::cos(2.48430783417 + 13341.6743113068 * t); + result += 1.65e-09 * std::cos(2.496679246 + 58953.145443294 * t); + result += 1.23e-09 * std::cos(3.45660563754 + 6277.552925684 * t); + result += 1.17e-09 * std::cos(0.86065134175 + 6245.0481773556 * t); + result += 1.49e-09 * std::cos(5.61358280963 + 5729.506447149 * t); + result += 1.53e-09 * std::cos(0.2686002995 + 245.8316462294 * t); + result += 1.28e-09 * std::cos(0.71204006588 + 103.0927742186 * t); + result += 1.59e-09 * std::cos(2.43166592149 + 221995.028801495 * t); + result += 1.3e-09 * std::cos(2.80707316718 + 6016.4688082696 * t); + result += 1.37e-09 * std::cos(1.70657709294 + 12566.08438968 * t); + result += 1.11e-09 * std::cos(1.56305648432 + 17782.7320727842 * t); + result += 1.13e-09 * std::cos(3.58302904101 + 25685.872802808 * t); + result += 1.09e-09 * std::cos(3.26403795962 + 6819.8803620868 * t); + result += 1.22e-09 * std::cos(0.34120688217 + 1162.4747044078 * t); + result += 1.19e-09 * std::cos(5.84644718278 + 12721.572099417 * t); + result += 1.44e-09 * std::cos(2.28899679126 + 12489.8856287072 * t); + result += 1.37e-09 * std::cos(5.82029768354 + 44809.6502008634 * t); + result += 1.07e-09 * std::cos(2.4281854414 + 5547.1993364596 * t); + result += 1.34e-09 * std::cos(1.26539982939 + 5331.3574437408 * t); + result += 1.03e-09 * std::cos(5.96518130595 + 6321.1035226272 * t); + result += 1.09e-09 * std::cos(0.33808549034 + 11300.5842213564 * t); + result += 1.29e-09 * std::cos(5.89187277327 + 12029.3471878874 * t); + result += 1.22e-09 * std::cos(5.77325634636 + 11919.140866668 * t); + result += 1.07e-09 * std::cos(6.2499898935 + 77690.7595057385 * t); + result += 1.07e-09 * std::cos(1.00535580713 + 77736.7834305025 * t); + result += 1.43e-09 * std::cos(0.24122178432 + 4214.0690150848 * t); + result += 1.43e-09 * std::cos(0.88529649733 + 7576.560073574 * t); + result += 1.07e-09 * std::cos(2.92124030496 + 31415.379249957 * t); + result += 9.9e-10 * std::cos(5.70862227072 + 5540.0857894588 * t); + result += 1.1e-09 * std::cos(0.37528037383 + 5863.5912061162 * t); + result += 1.04e-09 * std::cos(4.44107178366 + 2118.7638603784 * t); + result += 9.8e-10 * std::cos(5.95877916706 + 4061.2192153944 * t); + result += 1.13e-09 * std::cos(1.24206857385 + 84672.4758445047 * t); + result += 1.24e-09 * std::cos(2.55619029867 + 12539.853380183 * t); + result += 1.1e-09 * std::cos(3.66952094329 + 238004.524157236 * t); + result += 1.12e-09 * std::cos(4.32512422943 + 97238.6275444875 * t); + result += 9.7e-10 * std::cos(3.70151541181 + 11720.0688652316 * t); + result += 1.2e-09 * std::cos(1.26895630252 + 12043.574281889 * t); + result += 9.4e-10 * std::cos(2.56461130309 + 19004.6479494084 * t); + result += 1.17e-09 * std::cos(3.65425622684 + 34520.3093093808 * t); + result += 9.8e-10 * std::cos(0.13589994287 + 11080.1715789176 * t); + result += 9.7e-10 * std::cos(5.38330115253 + 7834.1210726394 * t); + result += 9.7e-10 * std::cos(2.46722096722 + 71980.6335747312 * t); + result += 9.5e-10 * std::cos(5.36958330451 + 6288.5987742988 * t); + result += 1.11e-09 * std::cos(5.01961920313 + 11823.1616394502 * t); + result += 9e-10 * std::cos(2.72299804525 + 26880.3198130326 * t); + result += 9.9e-10 * std::cos(0.90164266377 + 18635.9284545362 * t); + result += 1.26e-09 * std::cos(4.78722177847 + 305281.943071049 * t); + result += 9.3e-10 * std::cos(0.21240380046 + 18139.2945014159 * t); + result += 1.24e-09 * std::cos(5.00979495566 + 172146.97134054 * t); + result += 9.9e-10 * std::cos(5.67090026475 + 16522.6597160022 * t); + result += 9.2e-10 * std::cos(2.28180963676 + 12491.3701014155 * t); + result += 9e-10 * std::cos(4.50544881196 + 40077.61957352 * t); + result += 1e-09 * std::cos(2.00639461612 + 12323.4230960088 * t); + result += 9.5e-10 * std::cos(5.68801979087 + 14919.0178537546 * t); + result += 8.7e-10 * std::cos(1.86043406047 + 27707.5424942948 * t); + result += 1.05e-09 * std::cos(3.02903468417 + 22345.2603761082 * t); + result += 8.7e-10 * std::cos(5.43970168638 + 6272.0301497275 * t); + result += 8.9e-10 * std::cos(1.63389387182 + 33326.5787331742 * t); + result += 8.2e-10 * std::cos(5.58298993353 + 10241.2022911672 * t); + result += 9.4e-10 * std::cos(5.47749711149 + 9924.8104215106 * t); + result += 8.2e-10 * std::cos(4.71988314145 + 15141.390794312 * t); + result += 9.7e-10 * std::cos(5.61458778738 + 2787.0430238574 * t); + result += 9.6e-10 * std::cos(3.89073946348 + 6379.0550772092 * t); + result += 8.1e-10 * std::cos(3.13038482444 + 36147.4098773004 * t); + result += 1.1e-09 * std::cos(4.89978492291 + 72140.6286666874 * t); + result += 9.7e-10 * std::cos(5.20764563059 + 6303.4311693902 * t); + result += 8.2e-10 * std::cos(5.26342716139 + 9814.6041002912 * t); + result += 1.09e-09 * std::cos(2.3555558977 + 83286.9142695536 * t); + result += 9.7e-10 * std::cos(2.58492958057 + 30666.1549584328 * t); + result += 9.3e-10 * std::cos(1.32651591333 + 23020.653086588 * t); + result += 7.8e-10 * std::cos(3.99588630754 + 11293.4706743556 * t); + result += 9e-10 * std::cos(0.57771932738 + 26482.1708096244 * t); + result += 1.06e-09 * std::cos(3.92012705073 + 62883.3551395136 * t); + result += 9.8e-10 * std::cos(2.94397773524 + 316.3918696566 * t); + result += 7.6e-10 * std::cos(3.96310417608 + 29026.4852295078 * t); + result += 7.8e-10 * std::cos(1.97068529306 + 90279.9231681033 * t); + result += 7.6e-10 * std::cos(0.23027966596 + 21424.4666443034 * t); + result += 8e-10 * std::cos(2.23099742212 + 266.6070417218 * t); + result += 7.9e-10 * std::cos(1.46227790922 + 8982.810669309 * t); + result += 1.02e-09 * std::cos(4.92129953565 + 5621.8429232104 * t); + result += 1e-09 * std::cos(0.39243148321 + 24279.1070182136 * t); + result += 7.1e-10 * std::cos(1.52014858474 + 33794.5437235286 * t); + result += 7.6e-10 * std::cos(0.22880641443 + 57375.8019008462 * t); + result += 9.1e-10 * std::cos(0.96515913904 + 48739.859897083 * t); + result += 7.5e-10 * std::cos(2.77638585157 + 12964.300703391 * t); + result += 7.7e-10 * std::cos(5.18846946344 + 11520.9968637952 * t); + result += 6.8e-10 * std::cos(0.50006599129 + 4274.5183108324 * t); + result += 7.5e-10 * std::cos(2.07323762803 + 15664.0355227086 * t); + result += 7.4e-10 * std::cos(1.01884134928 + 6393.2821712108 * t); + result += 7.7e-10 * std::cos(0.4666517878 + 16207.886271502 * t); + result += 8.1e-10 * std::cos(4.10452219483 + 161710.618786232 * t); + result += 6.7e-10 * std::cos(3.83840630887 + 6262.7205305926 * t); + result += 7.1e-10 * std::cos(3.91415523291 + 7875.6718636242 * t); + result += 8.1e-10 * std::cos(0.91938383237 + 74.7815985673 * t); + result += 8.3e-10 * std::cos(4.69916218791 + 23006.4259925864 * t); + result += 6.3e-10 * std::cos(2.32556465878 + 6279.1945146334 * t); + result += 6.5e-10 * std::cos(5.41938745446 + 28628.3362260996 * t); + result += 6.5e-10 * std::cos(3.02336771694 + 5959.570433334 * t); + result += 6.4e-10 * std::cos(3.3103319837 + 2636.725472637 * t); + result += 6.4e-10 * std::cos(0.18375587519 + 1066.49547719 * t); + result += 8e-10 * std::cos(5.81239171612 + 12341.8069042809 * t); + result += 6.6e-10 * std::cos(2.15105504851 + 38.0276726358 * t); + result += 6.2e-10 * std::cos(2.43313614978 + 10138.1095169486 * t); + result += 6e-10 * std::cos(3.1615390647 + 5490.300961524 * t); + result += 6.9e-10 * std::cos(0.30764736334 + 7018.9523635232 * t); + result += 6.8e-10 * std::cos(2.24442548639 + 24383.0791084414 * t); + result += 7.8e-10 * std::cos(1.39649386463 + 9411.4646150872 * t); + result += 6.3e-10 * std::cos(0.72976362625 + 6286.9571853494 * t); + result += 7.3e-10 * std::cos(4.95125917731 + 6453.7487206106 * t); + result += 7.8e-10 * std::cos(0.32736023459 + 6528.9074962208 * t); + result += 5.9e-10 * std::cos(4.95362151577 + 35707.7100829074 * t); + result += 7e-10 * std::cos(2.37962727525 + 15508.6151232744 * t); + result += 7.3e-10 * std::cos(1.35229143111 + 5327.4761083828 * t); + result += 7.2e-10 * std::cos(5.91833527334 + 10881.0995774812 * t); + result += 5.9e-10 * std::cos(5.36231868425 + 10239.5838660108 * t); + result += 5.9e-10 * std::cos(1.63156134967 + 61306.0115970658 * t); + result += 5.4e-10 * std::cos(4.29491690425 + 21947.1113727 * t); + result += 5.7e-10 * std::cos(5.89190132575 + 34513.2630726828 * t); + result += 7.4e-10 * std::cos(1.38235845304 + 9967.4538999816 * t); + result += 5.3e-10 * std::cos(3.86543309344 + 32370.9789915656 * t); + result += 5.5e-10 * std::cos(4.51794544854 + 34911.412076091 * t); + result += 6.3e-10 * std::cos(5.41479412056 + 11502.8376165305 * t); + result += 6.3e-10 * std::cos(2.34416220742 + 11510.7019230567 * t); + result += 6.8e-10 * std::cos(0.77493931112 + 29864.334027309 * t); + result += 6e-10 * std::cos(5.57024703495 + 5756.9080032458 * t); + result += 7.2e-10 * std::cos(2.80863088166 + 10866.8724834796 * t); + result += 6.1e-10 * std::cos(2.69736991384 + 82576.9812209953 * t); + result += 6.3e-10 * std::cos(5.32068807257 + 3116.6594122598 * t); + result += 5.2e-10 * std::cos(1.02278758099 + 6272.4391846416 * t); + result += 6.9e-10 * std::cos(5.00698550308 + 25287.7237993998 * t); + result += 6.6e-10 * std::cos(6.12047940728 + 12074.488407524 * t); + result += 5.1e-10 * std::cos(2.59519527563 + 11396.5634485742 * t); + result += 5.6e-10 * std::cos(2.57995973521 + 17892.9383940036 * t); + result += 5.9e-10 * std::cos(0.4416723762 + 250570.675857219 * t); + result += 5.9e-10 * std::cos(3.84070143543 + 5483.254724826 * t); + result += 4.9e-10 * std::cos(0.54704693048 + 22594.054895712 * t); + result += 6.5e-10 * std::cos(2.38423614501 + 52670.0695933026 * t); + result += 6.9e-10 * std::cos(5.34363738671 + 66813.5648357332 * t); + result += 5.7e-10 * std::cos(5.42770501007 + 310145.152823924 * t); + result += 5.3e-10 * std::cos(1.17760296075 + 149.5631971346 * t); + result += 6.1e-10 * std::cos(4.02090887211 + 34596.3646546524 * t); + result += 4.9e-10 * std::cos(4.18361320516 + 18606.4989460002 * t); + result += 5.5e-10 * std::cos(0.83886167974 + 20452.8694122218 * t); + result += 5e-10 * std::cos(1.46327331958 + 37455.7264959744 * t); + result += 4.8e-10 * std::cos(4.53854727167 + 29822.7832363242 * t); + result += 5.8e-10 * std::cos(3.34847975377 + 33990.6183442862 * t); + result += 6.5e-10 * std::cos(1.45522693982 + 76251.3277706202 * t); + result += 5.6e-10 * std::cos(2.35650663692 + 37724.7534197482 * t); + result += 5.2e-10 * std::cos(2.61551081496 + 5999.2165311262 * t); + result += 5.3e-10 * std::cos(0.17334326094 + 77717.2945864695 * t); + result += 5.3e-10 * std::cos(0.79879700631 + 77710.2483497715 * t); + result += 4.7e-10 * std::cos(0.43240779709 + 735.8765135318 * t); + result += 5.3e-10 * std::cos(4.58763261686 + 11616.976091013 * t); + result += 4.8e-10 * std::cos(6.20230111054 + 4171.4255366138 * t); + result += 5.2e-10 * std::cos(1.09723616404 + 640.8776073822 * t); + result += 5.7e-10 * std::cos(3.42008310383 + 50317.2034395308 * t); + result += 5.3e-10 * std::cos(1.01528448581 + 149144.46708625 * t); + result += 4.7e-10 * std::cos(3.00924906195 + 52175.8062831484 * t); + result += 5.2e-10 * std::cos(2.03254070404 + 6293.7125153412 * t); + result += 4.8e-10 * std::cos(0.12356889734 + 13362.4497067992 * t); + result += 4.5e-10 * std::cos(3.37963782356 + 10763.779709261 * t); + result += 4.7e-10 * std::cos(5.50981287869 + 12779.4507954208 * t); + result += 6.2e-10 * std::cos(5.45209070099 + 949.1756089698 * t); + result += 6.1e-10 * std::cos(2.93237974631 + 5791.4125575326 * t); + result += 4.4e-10 * std::cos(2.87440620802 + 8584.6616659008 * t); + result += 4.6e-10 * std::cos(4.0314179656 + 10667.8004820432 * t); + result += 4.7e-10 * std::cos(3.89902931422 + 3903.9113764198 * t); + result += 4.6e-10 * std::cos(2.75700467329 + 6993.0088985497 * t); + result += 4.5e-10 * std::cos(1.933862933 + 206.1855484372 * t); + result += 4.7e-10 * std::cos(2.57670800912 + 11492.542675792 * t); + result += 4.4e-10 * std::cos(3.62570223167 + 63658.8777508376 * t); + result += 5.1e-10 * std::cos(0.84536826273 + 12345.739057544 * t); + result += 4.3e-10 * std::cos(0.01524970172 + 37853.8754993826 * t); + result += 4.1e-10 * std::cos(3.27146326065 + 8858.3149443206 * t); + result += 4.5e-10 * std::cos(3.03765521215 + 65236.2212932854 * t); + result += 4.7e-10 * std::cos(1.44447548944 + 21393.5419698576 * t); + result += 5.8e-10 * std::cos(5.45843180927 + 1975.492545856 * t); + result += 5e-10 * std::cos(2.13285524146 + 12573.2652469836 * t); + result += 4.1e-10 * std::cos(1.32190847146 + 2547.8375382324 * t); + result += 4.7e-10 * std::cos(3.67579608544 + 28313.288804661 * t); + result += 4.1e-10 * std::cos(2.24013475126 + 8273.8208670324 * t); + result += 4.7e-10 * std::cos(6.21438985953 + 10991.3058987006 * t); + result += 4.2e-10 * std::cos(3.0163181735 + 853.196381752 * t); + result += 5.6e-10 * std::cos(1.09773690181 + 77376.2010224076 * t); + result += 4e-10 * std::cos(2.35698541041 + 2699.7348193176 * t); + result += 4.3e-10 * std::cos(5.28030898459 + 17796.9591667858 * t); + result += 5.4e-10 * std::cos(2.59175932091 + 22910.4467653686 * t); + result += 5.4e-10 * std::cos(0.88027764102 + 71960.3865832237 * t); + result += 5.5e-10 * std::cos(0.07988899477 + 83467.1563530173 * t); + result += 3.9e-10 * std::cos(1.12867321442 + 9910.583327509 * t); + result += 4e-10 * std::cos(1.35670430524 + 27177.8515292002 * t); + result += 3.9e-10 * std::cos(4.39624220245 + 5618.3198048614 * t); + result += 4.2e-10 * std::cos(4.78798367468 + 7856.89627409019 * t); + result += 4.7e-10 * std::cos(2.75482175292 + 18202.2167166594 * t); + result += 3.9e-10 * std::cos(1.97008298629 + 24491.4257925834 * t); + result += 4.2e-10 * std::cos(4.04346599946 + 7863.9425107882 * t); + result += 3.8e-10 * std::cos(0.49178679251 + 38650.173506199 * t); + result += 3.6e-10 * std::cos(4.86047906533 + 4157.1984426122 * t); + result += 4.3e-10 * std::cos(5.64354880978 + 1062.9050485382 * t); + result += 3.6e-10 * std::cos(3.98066313627 + 12565.1713789146 * t); + result += 4.2e-10 * std::cos(2.30753932657 + 6549.6828917132 * t); + result += 4e-10 * std::cos(5.3969491832 + 9498.2122306346 * t); + result += 4e-10 * std::cos(3.30603243754 + 23536.116957681 * t); + result += 5e-10 * std::cos(6.15760345261 + 78051.3419138334 * t); + return result; +} + +constexpr double getEarthR1(double t) +{ + double result = 0.0; + result += 0.00103018608 * std::cos(1.10748969588 + 6283.0758499914 * t); + result += 1.721238e-05 * std::cos(1.06442301418 + 12566.1516999828 * t); + result -= -7.02215e-06; + result += 3.2346e-07 * std::cos(1.02169059149 + 18849.2275499742 * t); + result += 3.0799e-07 * std::cos(2.84353804832 + 5507.5532386674 * t); + result += 2.4971e-07 * std::cos(1.31906709482 + 5223.6939198022 * t); + result += 1.8485e-07 * std::cos(1.42429748614 + 1577.3435424478 * t); + result += 1.0078e-07 * std::cos(5.91378194648 + 10977.078804699 * t); + result += 8.634e-08 * std::cos(0.27146150602 + 5486.777843175 * t); + result += 8.654e-08 * std::cos(1.42046854427 + 6275.9623029906 * t); + result += 5.069e-08 * std::cos(1.68613426734 + 5088.6288397668 * t); + result += 4.985e-08 * std::cos(6.01401770704 + 6286.5989683404 * t); + result += 4.669e-08 * std::cos(5.98724494073 + 529.6909650946 * t); + result += 4.395e-08 * std::cos(0.51800238019 + 4694.0029547076 * t); + result += 3.872e-08 * std::cos(4.74969833437 + 2544.3144198834 * t); + result += 3.75e-08 * std::cos(5.07097685568 + 796.2980068164 * t); + result += 4.1e-08 * std::cos(1.08424786092 + 9437.762934887 * t); + result += 3.518e-08 * std::cos(0.02290216272 + 83996.8473181119 * t); + result += 3.436e-08 * std::cos(0.94937019624 + 71430.6956181291 * t); + result += 3.221e-08 * std::cos(6.15628775313 + 2146.1654164752 * t); + result += 3.414e-08 * std::cos(5.41218322538 + 775.522611324 * t); + result += 2.863e-08 * std::cos(5.48432847146 + 10447.3878396044 * t); + result += 2.52e-08 * std::cos(0.24276941146 + 398.1490034082 * t); + result += 2.201e-08 * std::cos(4.95216196651 + 6812.766815086 * t); + result += 2.186e-08 * std::cos(0.41991743105 + 8031.0922630584 * t); + result += 2.838e-08 * std::cos(3.42034351366 + 2352.8661537718 * t); + result += 2.554e-08 * std::cos(6.13241878525 + 6438.4962494256 * t); + result += 1.932e-08 * std::cos(5.31374608366 + 8429.2412664666 * t); + result += 2.429e-08 * std::cos(3.09164528262 + 4690.4798363586 * t); + result += 1.73e-08 * std::cos(1.5368620855 + 4705.7323075436 * t); + result += 2.25e-08 * std::cos(3.68863633842 + 7084.8967811152 * t); + result += 2.093e-08 * std::cos(1.28191783032 + 1748.016413067 * t); + result += 1.441e-08 * std::cos(0.81656250862 + 14143.4952424306 * t); + result += 1.483e-08 * std::cos(3.22225357771 + 7234.794256242 * t); + result += 1.754e-08 * std::cos(3.22883705112 + 6279.5527316424 * t); + result += 1.583e-08 * std::cos(4.09702349428 + 11499.6562227928 * t); + result += 1.575e-08 * std::cos(5.53890170575 + 3154.6870848956 * t); + result += 1.847e-08 * std::cos(1.82040335363 + 7632.9432596502 * t); + result += 1.504e-08 * std::cos(3.63293385726 + 11513.8833167944 * t); + result += 1.337e-08 * std::cos(4.64440864339 + 6836.6452528338 * t); + result += 1.275e-08 * std::cos(2.69341415363 + 1349.8674096588 * t); + result += 1.352e-08 * std::cos(6.15101580257 + 5746.271337896 * t); + result += 1.125e-08 * std::cos(3.35673439497 + 17789.845619785 * t); + result += 1.47e-08 * std::cos(3.65282991755 + 1194.4470102246 * t); + result += 1.177e-08 * std::cos(2.57676109092 + 13367.9726311066 * t); + result += 1.101e-08 * std::cos(4.49748696552 + 4292.3308329504 * t); + result += 1.234e-08 * std::cos(5.65036509521 + 5760.4984318976 * t); + result += 9.84e-09 * std::cos(0.65517395136 + 5856.4776591154 * t); + result += 9.28e-09 * std::cos(2.32420318751 + 10213.285546211 * t); + result += 1.077e-08 * std::cos(5.82812169132 + 12036.4607348882 * t); + result += 9.16e-09 * std::cos(0.76613009583 + 16730.4636895958 * t); + result += 8.77e-09 * std::cos(1.50137505051 + 11926.2544136688 * t); + result += 1.023e-08 * std::cos(5.62076589825 + 6256.7775301916 * t); + result += 8.51e-09 * std::cos(0.65709335533 + 155.4203994342 * t); + result += 8.02e-09 * std::cos(4.10519132088 + 951.7184062506 * t); + result += 8.57e-09 * std::cos(1.41661697538 + 5753.3848848968 * t); + result += 9.94e-09 * std::cos(1.14418521187 + 1059.3819301892 * t); + result += 8.13e-09 * std::cos(1.63948433322 + 6681.2248533996 * t); + result += 6.62e-09 * std::cos(4.5520045226 + 5216.5803728014 * t); + result += 6.44e-09 * std::cos(4.19478168733 + 6040.3472460174 * t); + result += 6.26e-09 * std::cos(1.50767713598 + 5643.1785636774 * t); + result += 5.9e-09 * std::cos(6.18277145205 + 4164.311989613 * t); + result += 6.35e-09 * std::cos(0.52413263542 + 6290.1893969922 * t); + result += 6.5e-09 * std::cos(0.9793569035 + 25132.3033999656 * t); + result += 5.68e-09 * std::cos(2.30125315873 + 10973.55568635 * t); + result += 5.47e-09 * std::cos(5.27256412213 + 3340.6124266998 * t); + result += 5.47e-09 * std::cos(2.20144422886 + 1592.5960136328 * t); + result += 5.26e-09 * std::cos(0.92464258226 + 11371.7046897582 * t); + result += 4.9e-09 * std::cos(5.90951388655 + 3894.1818295422 * t); + result += 4.78e-09 * std::cos(1.66857963179 + 12168.0026965746 * t); + result += 5.16e-09 * std::cos(3.59803483887 + 10969.9652576982 * t); + result += 5.18e-09 * std::cos(3.97914412373 + 17298.1823273262 * t); + result += 5.34e-09 * std::cos(5.03740926442 + 9917.6968745098 * t); + result += 4.87e-09 * std::cos(2.50545369269 + 6127.6554505572 * t); + result += 4.16e-09 * std::cos(4.04828175503 + 10984.1923516998 * t); + result += 5.38e-09 * std::cos(5.54081539805 + 553.5694028424 * t); + result += 4.02e-09 * std::cos(2.16544019233 + 7860.4193924392 * t); + result += 5.53e-09 * std::cos(2.32177369366 + 11506.7697697936 * t); + result += 3.67e-09 * std::cos(3.3915253225 + 6496.3749454294 * t); + result += 3.6e-09 * std::cos(5.34379853282 + 7079.3738568078 * t); + result += 3.37e-09 * std::cos(3.61563704045 + 11790.6290886588 * t); + result += 4.56e-09 * std::cos(0.30754294809 + 801.8209311238 * t); + result += 4.17e-09 * std::cos(3.70009308674 + 10575.4066829418 * t); + result += 3.81e-09 * std::cos(5.82033971802 + 7058.5984613154 * t); + result += 3.21e-09 * std::cos(0.31988767355 + 16200.7727245012 * t); + result += 3.64e-09 * std::cos(1.08414306177 + 6309.3741697912 * t); + result += 2.94e-09 * std::cos(4.54798604957 + 11856.2186514245 * t); + result += 2.9e-09 * std::cos(1.26473978562 + 8635.9420037632 * t); + result += 3.99e-09 * std::cos(4.16998866302 + 26.2983197998 * t); + result += 2.62e-09 * std::cos(5.08316906342 + 10177.2576795336 * t); + result += 2.43e-09 * std::cos(2.2574609119 + 11712.9553182308 * t); + result += 2.37e-09 * std::cos(1.05070575346 + 242.728603974 * t); + result += 2.75e-09 * std::cos(3.45319481756 + 5884.9268465832 * t); + result += 2.55e-09 * std::cos(5.38496831087 + 21228.3920235458 * t); + result += 3.07e-09 * std::cos(4.24313526604 + 3738.761430108 * t); + result += 2.16e-09 * std::cos(3.46037894728 + 213.299095438 * t); + result += 1.96e-09 * std::cos(0.69029243914 + 1990.745017041 * t); + result += 1.98e-09 * std::cos(5.16301829964 + 12352.8526045448 * t); + result += 2.14e-09 * std::cos(3.91876200279 + 13916.0191096416 * t); + result += 2.12e-09 * std::cos(4.00861198517 + 5230.807466803 * t); + result += 1.84e-09 * std::cos(5.59805976614 + 6283.14316029419 * t); + result += 1.84e-09 * std::cos(2.85275392124 + 7238.6755916 * t); + result += 1.79e-09 * std::cos(2.54259058334 + 14314.1681130498 * t); + result += 2.25e-09 * std::cos(1.64458698399 + 4732.0306273434 * t); + result += 2.36e-09 * std::cos(5.58826125715 + 6069.7767545534 * t); + result += 1.87e-09 * std::cos(2.72805985443 + 6062.6632075526 * t); + result += 1.84e-09 * std::cos(6.04216273598 + 6283.0085396886 * t); + result += 2.3e-09 * std::cos(3.62591335086 + 6284.0561710596 * t); + result += 1.63e-09 * std::cos(2.19117396803 + 18073.7049386502 * t); + result += 1.72e-09 * std::cos(0.9761295074 + 3930.2096962196 * t); + result += 2.15e-09 * std::cos(1.04672844028 + 3496.032826134 * t); + result += 1.69e-09 * std::cos(4.75084479006 + 17267.2682016912 * t); + result += 1.52e-09 * std::cos(0.19390712179 + 9779.1086761254 * t); + result += 1.82e-09 * std::cos(5.16288118255 + 17253.0411076896 * t); + result += 1.49e-09 * std::cos(0.8094418426 + 709.9330485583 * t); + result += 1.63e-09 * std::cos(2.1920957039 + 6076.8903015542 * t); + result += 1.86e-09 * std::cos(5.01159497089 + 11015.1064773348 * t); + result += 1.34e-09 * std::cos(0.97765485759 + 65147.6197681377 * t); + result += 1.41e-09 * std::cos(4.38421981312 + 4136.9104335162 * t); + result += 1.58e-09 * std::cos(4.60974280627 + 9623.6882766912 * t); + result += 1.33e-09 * std::cos(3.30508592837 + 154717.609887683 * t); + result += 1.63e-09 * std::cos(6.11782626245 + 3.523118349 * t); + result += 1.74e-09 * std::cos(1.58078542187 + 7.1135470008 * t); + result += 1.41e-09 * std::cos(0.49976927274 + 25158.6017197654 * t); + result += 1.24e-09 * std::cos(6.03440460031 + 9225.539273283 * t); + result += 1.5e-09 * std::cos(5.30166336812 + 13517.8701062334 * t); + result += 1.27e-09 * std::cos(1.92389511438 + 22483.8485744926 * t); + result += 1.21e-09 * std::cos(2.37813129011 + 167283.761587666 * t); + result += 1.2e-09 * std::cos(3.98423684853 + 4686.8894077068 * t); + result += 1.17e-09 * std::cos(5.81072642211 + 12569.6748183318 * t); + result += 1.22e-09 * std::cos(5.60973054224 + 5642.1982426092 * t); + result += 1.57e-09 * std::cos(3.40236426002 + 16496.3613962024 * t); + result += 1.29e-09 * std::cos(2.10705116371 + 1589.0728952838 * t); + result += 1.16e-09 * std::cos(0.55839966736 + 5849.3641121146 * t); + result += 1.23e-09 * std::cos(1.52961392771 + 12559.038152982 * t); + result += 1.11e-09 * std::cos(0.44848279675 + 6172.869528772 * t); + result += 1.23e-09 * std::cos(5.81645568991 + 6282.0955289232 * t); + result += 1.5e-09 * std::cos(4.26278409223 + 3128.3887650958 * t); + result += 1.06e-09 * std::cos(2.27437761356 + 5429.8794682394 * t); + result += 1.04e-09 * std::cos(4.42743707728 + 23543.2305046818 * t); + result += 1.21e-09 * std::cos(0.39459045915 + 12132.439962106 * t); + result += 1.04e-09 * std::cos(2.41842602527 + 426.598190876 * t); + result += 1.1e-09 * std::cos(5.80381480447 + 16858.4825329332 * t); + result += 1e-09 * std::cos(2.93805577485 + 4535.0594369244 * t); + result += 9.7e-10 * std::cos(3.97935904984 + 6133.5126528568 * t); + result += 1.1e-09 * std::cos(6.22339014386 + 12146.6670561076 * t); + result += 9.8e-10 * std::cos(0.87576563709 + 6525.8044539654 * t); + result += 9.8e-10 * std::cos(3.15248421301 + 10440.2742926036 * t); + result += 9.5e-10 * std::cos(2.461684111 + 3097.88382272579 * t); + result += 8.8e-10 * std::cos(0.23371480284 + 13119.7211028252 * t); + result += 9.8e-10 * std::cos(5.77016493489 + 7342.4577801806 * t); + result += 9.2e-10 * std::cos(6.03915555063 + 20426.571092422 * t); + result += 9.6e-10 * std::cos(5.56909292561 + 2388.8940204492 * t); + result += 8.1e-10 * std::cos(1.32131147691 + 5650.2921106782 * t); + result += 8.6e-10 * std::cos(3.94529200528 + 10454.5013866052 * t); + result += 7.6e-10 * std::cos(2.70729716925 + 143571.324284816 * t); + result += 9.1e-10 * std::cos(5.64100034152 + 8827.3902698748 * t); + result += 7.6e-10 * std::cos(1.80783856698 + 28286.9904848612 * t); + result += 8.1e-10 * std::cos(1.90858992196 + 29088.811415985 * t); + result += 7.5e-10 * std::cos(3.40955892978 + 5481.2549188676 * t); + result += 6.9e-10 * std::cos(4.49936170873 + 17256.6315363414 * t); + result += 8.8e-10 * std::cos(1.10098454357 + 11769.8536931664 * t); + result += 6.6e-10 * std::cos(2.78285801977 + 536.8045120954 * t); + result += 6.8e-10 * std::cos(3.88179770758 + 17260.1546546904 * t); + result += 8.4e-10 * std::cos(1.59303306354 + 9380.9596727172 * t); + result += 8.8e-10 * std::cos(3.88076636762 + 7477.522860216 * t); + result += 6.1e-10 * std::cos(6.17558202197 + 11087.2851259184 * t); + result += 6e-10 * std::cos(4.34824715818 + 6206.8097787158 * t); + result += 8.2e-10 * std::cos(4.59843208943 + 9388.0059094152 * t); + result += 7.9e-10 * std::cos(1.63131230601 + 4933.2084403326 * t); + result += 7.8e-10 * std::cos(4.20905757484 + 5729.506447149 * t); + result += 5.7e-10 * std::cos(5.48157926651 + 18319.5365848796 * t); + result += 6e-10 * std::cos(1.01261781084 + 12721.572099417 * t); + result += 5.6e-10 * std::cos(1.63031935692 + 15720.8387848784 * t); + result += 5.5e-10 * std::cos(0.24926735018 + 15110.4661198662 * t); + result += 6.1e-10 * std::cos(5.93059279661 + 12539.853380183 * t); + result += 5.5e-10 * std::cos(4.84298966314 + 13095.8426650774 * t); + result += 6.7e-10 * std::cos(6.11690589247 + 8662.240323563 * t); + result += 5.4e-10 * std::cos(5.73750638571 + 3634.6210245184 * t); + result += 7.4e-10 * std::cos(1.05466745829 + 16460.333529525 * t); + result += 5.3e-10 * std::cos(2.29084335688 + 16062.1845261168 * t); + result += 6.4e-10 * std::cos(2.13513767927 + 7875.6718636242 * t); + result += 6.7e-10 * std::cos(0.07096807518 + 14945.3161735544 * t); + result += 5.1e-10 * std::cos(2.31511194429 + 6262.7205305926 * t); + result += 5.7e-10 * std::cos(5.77055471237 + 12043.574281889 * t); + result += 5.6e-10 * std::cos(4.41980790431 + 4701.1165017084 * t); + result += 5.9e-10 * std::cos(5.87963500073 + 5331.3574437408 * t); + result += 5.8e-10 * std::cos(2.30546168628 + 955.5997416086 * t); + result += 4.9e-10 * std::cos(1.93839278478 + 5333.9002410216 * t); + result += 4.8e-10 * std::cos(2.69973662261 + 6709.6740408674 * t); + result += 6.4e-10 * std::cos(1.64379897981 + 6262.300454499 * t); + result += 4.6e-10 * std::cos(3.98449608961 + 98068.5367163054 * t); + result += 5e-10 * std::cos(3.68875893005 + 12323.4230960088 * t); + result += 4.5e-10 * std::cos(3.30068569697 + 22003.9146348698 * t); + result += 4.7e-10 * std::cos(1.26317154881 + 11919.140866668 * t); + result += 4.5e-10 * std::cos(0.89150445122 + 51868.2486621788 * t); + result += 4.3e-10 * std::cos(1.61526242998 + 6277.552925684 * t); + result += 4.3e-10 * std::cos(5.74295325645 + 11403.676995575 * t); + result += 4.4e-10 * std::cos(3.43070646822 + 10021.8372800994 * t); + result += 5.6e-10 * std::cos(0.02481833774 + 15671.0817594066 * t); + result += 5.5e-10 * std::cos(3.14274403422 + 33019.0211122046 * t); + result += 4.5e-10 * std::cos(3.00877289177 + 8982.810669309 * t); + result += 4.6e-10 * std::cos(0.73303568429 + 6303.4311693902 * t); + result += 4.9e-10 * std::cos(1.60455690285 + 6303.8512454838 * t); + result += 4.5e-10 * std::cos(0.40210030323 + 6805.6532680852 * t); + result += 5.3e-10 * std::cos(0.94869680175 + 10988.808157535 * t); + result += 4.1e-10 * std::cos(1.61122384329 + 6819.8803620868 * t); + result += 5.5e-10 * std::cos(0.89439119424 + 11933.3679606696 * t); + result += 4.5e-10 * std::cos(3.88495384656 + 60530.4889857418 * t); + result += 4e-10 * std::cos(4.75740908001 + 38526.574350872 * t); + result += 4e-10 * std::cos(1.49921251887 + 18451.078546566 * t); + result += 4e-10 * std::cos(3.77498297228 + 26087.9031415742 * t); + result += 5.1e-10 * std::cos(1.70258603562 + 1551.045222648 * t); + result += 3.9e-10 * std::cos(2.97100699926 + 2118.7638603784 * t); + result += 5.3e-10 * std::cos(5.19854123078 + 77713.7714681205 * t); + result += 4.7e-10 * std::cos(4.26356628717 + 21424.4666443034 * t); + result += 3.7e-10 * std::cos(0.62902722802 + 24356.7807886416 * t); + result += 3.6e-10 * std::cos(0.11087914947 + 10344.2950653858 * t); + result += 3.6e-10 * std::cos(0.77037556319 + 12029.3471878874 * t); + result += 3.5e-10 * std::cos(3.30933994515 + 24072.9214697764 * t); + result += 3.5e-10 * std::cos(5.93650887012 + 31570.7996493912 * t); + result += 3.6e-10 * std::cos(2.15108874765 + 30774.5016425748 * t); + result += 3.6e-10 * std::cos(1.75078825382 + 16207.886271502 * t); + result += 3.3e-10 * std::cos(5.06264177921 + 226858.23855437 * t); + result += 3.4e-10 * std::cos(6.168913788 + 24491.4257925834 * t); + result += 3.5e-10 * std::cos(3.19120695549 + 32217.2001810808 * t); + result += 3.4e-10 * std::cos(2.31528650443 + 55798.4583583984 * t); + result += 3.2e-10 * std::cos(4.21446357042 + 15664.0355227086 * t); + result += 3.9e-10 * std::cos(1.24979117796 + 6418.1409300268 * t); + result += 3.7e-10 * std::cos(4.1194365577 + 2787.0430238574 * t); + result += 3.2e-10 * std::cos(1.6288771089 + 639.897286314 * t); + result += 3.8e-10 * std::cos(5.89832942685 + 640.8776073822 * t); + result += 3.2e-10 * std::cos(1.72442327688 + 27433.889215875 * t); + result += 3.1e-10 * std::cos(2.78828943753 + 12139.5535091068 * t); + result += 3.5e-10 * std::cos(4.44608896525 + 18202.2167166594 * t); + result += 3.4e-10 * std::cos(3.96287980676 + 18216.443810661 * t); + result += 3.3e-10 * std::cos(4.73611335874 + 16723.350142595 * t); + result += 3.4e-10 * std::cos(1.43910280005 + 49515.382508407 * t); + result += 3.1e-10 * std::cos(0.23302920161 + 23581.2581773176 * t); + result += 2.9e-10 * std::cos(2.0263384022 + 11609.8625440122 * t); + result += 3e-10 * std::cos(2.5492323024 + 9924.8104215106 * t); + result += 3.2e-10 * std::cos(4.91793198558 + 11300.5842213564 * t); + result += 2.8e-10 * std::cos(0.26187189577 + 13521.7514415914 * t); + result += 2.8e-10 * std::cos(3.84568936822 + 2699.7348193176 * t); + result += 2.9e-10 * std::cos(1.83149729794 + 29822.7832363242 * t); + result += 3.3e-10 * std::cos(4.60320094415 + 19004.6479494084 * t); + result += 2.7e-10 * std::cos(4.46183450287 + 6702.5604938666 * t); + result += 3e-10 * std::cos(4.4649407224 + 36147.4098773004 * t); + result += 2.7e-10 * std::cos(0.03211931363 + 6279.7894925736 * t); + result += 2.6e-10 * std::cos(5.46497324333 + 6245.0481773556 * t); + result += 3.5e-10 * std::cos(4.52695674113 + 36949.2308084242 * t); + result += 2.7e-10 * std::cos(3.52528177609 + 10770.8932562618 * t); + result += 2.6e-10 * std::cos(1.48499438453 + 11080.1715789176 * t); + result += 3.5e-10 * std::cos(2.82154380962 + 19402.7969528166 * t); + result += 2.5e-10 * std::cos(2.46339998836 + 6279.4854213396 * t); + result += 2.6e-10 * std::cos(4.97688894643 + 16737.5772365966 * t); + result += 2.6e-10 * std::cos(2.36136541526 + 17996.0311682222 * t); + result += 2.9e-10 * std::cos(4.15148654061 + 45892.730433157 * t); + result += 2.6e-10 * std::cos(4.50714272714 + 17796.9591667858 * t); + result += 2.7e-10 * std::cos(4.72625223674 + 1066.49547719 * t); + result += 2.5e-10 * std::cos(2.89309528854 + 6286.6662786432 * t); + result += 2.7e-10 * std::cos(0.37462444357 + 12964.300703391 * t); + result += 2.9e-10 * std::cos(4.94860010533 + 5863.5912061162 * t); + result += 3.1e-10 * std::cos(3.93096113577 + 29864.334027309 * t); + result += 2.4e-10 * std::cos(6.14987193584 + 18606.4989460002 * t); + result += 2.4e-10 * std::cos(3.74225964547 + 29026.4852295078 * t); + result += 2.5e-10 * std::cos(5.70460621565 + 27707.5424942948 * t); + result += 2.5e-10 * std::cos(5.33928840652 + 15141.390794312 * t); + result += 2.7e-10 * std::cos(3.0232089714 + 6286.3622074092 * t); + result += 2.3e-10 * std::cos(0.28364955406 + 5327.4761083828 * t); + result += 2.6e-10 * std::cos(1.34240461687 + 18875.525869774 * t); + result += 2.4e-10 * std::cos(1.33998410121 + 19800.9459562248 * t); + result += 2.5e-10 * std::cos(6.00172494004 + 6489.2613984286 * t); + result += 2.2e-10 * std::cos(1.81777974484 + 6288.5987742988 * t); + result += 2.2e-10 * std::cos(3.5860360664 + 6915.8595893046 * t); + result += 2.9e-10 * std::cos(2.09564449439 + 15265.8865193004 * t); + result += 2.2e-10 * std::cos(1.02173599251 + 11925.2740926006 * t); + result += 2.2e-10 * std::cos(4.74660932338 + 28230.1872226914 * t); + result += 2.1e-10 * std::cos(2.30688751432 + 5999.2165311262 * t); + result += 2.1e-10 * std::cos(3.2265494443 + 25934.1243310894 * t); + result += 2.1e-10 * std::cos(3.04956726238 + 6566.9351688566 * t); + result += 2.7e-10 * std::cos(5.35653084499 + 33794.5437235286 * t); + result += 2.8e-10 * std::cos(3.91168324815 + 18208.349942592 * t); + result += 2e-10 * std::cos(1.52296293311 + 135.0650800354 * t); + result += 2.2e-10 * std::cos(4.66462839521 + 13362.4497067992 * t); + result += 1.9e-10 * std::cos(1.78121167862 + 156137.475984799 * t); + result += 1.9e-10 * std::cos(2.99969102221 + 19651.048481098 * t); + result += 1.9e-10 * std::cos(2.86664273362 + 18422.6293590982 * t); + result += 2.5e-10 * std::cos(0.94995632141 + 31415.379249957 * t); + result += 1.9e-10 * std::cos(4.71432851499 + 77690.7595057385 * t); + result += 1.9e-10 * std::cos(2.54227398241 + 77736.7834305025 * t); + result += 2e-10 * std::cos(5.91915117116 + 48739.859897083 * t); + return result; +} + +constexpr double getEarthR2(double t) +{ + double result = 0.0; + result += 4.359385e-05 * std::cos(5.78455133738 + 6283.0758499914 * t); + result += 1.23633e-06 * std::cos(5.57934722157 + 12566.1516999828 * t); + result -= -1.2341e-07; + result += 8.792e-08 * std::cos(3.62777733395 + 77713.7714681205 * t); + result += 5.689e-08 * std::cos(1.86958905084 + 5573.1428014331 * t); + result += 3.301e-08 * std::cos(5.47027913302 + 18849.2275499742 * t); + result += 1.471e-08 * std::cos(4.48028885617 + 5507.5532386674 * t); + result += 1.013e-08 * std::cos(2.81456417694 + 5223.6939198022 * t); + result += 8.54e-09 * std::cos(3.10878241236 + 1577.3435424478 * t); + result += 1.102e-08 * std::cos(2.84173992403 + 161000.685737674 * t); + result += 6.48e-09 * std::cos(5.47349498544 + 775.522611324 * t); + result += 6.09e-09 * std::cos(1.37969434104 + 6438.4962494256 * t); + result += 4.99e-09 * std::cos(4.4164924225 + 6286.5989683404 * t); + result += 4.17e-09 * std::cos(0.90242451175 + 10977.078804699 * t); + result += 4.02e-09 * std::cos(3.2037658529 + 5088.6288397668 * t); + result += 3.51e-09 * std::cos(1.8107922777 + 5486.777843175 * t); + result += 4.67e-09 * std::cos(3.65753702738 + 7084.8967811152 * t); + result += 4.58e-09 * std::cos(5.38585314743 + 149854.400134808 * t); + result += 3.04e-09 * std::cos(3.51701098693 + 796.2980068164 * t); + result += 2.66e-09 * std::cos(6.17413982699 + 6836.6452528338 * t); + result += 2.79e-09 * std::cos(1.84120501086 + 4694.0029547076 * t); + result += 2.6e-09 * std::cos(1.41629543251 + 2146.1654164752 * t); + result += 2.66e-09 * std::cos(3.13832905677 + 71430.6956181291 * t); + result += 3.21e-09 * std::cos(5.35313367048 + 3154.6870848956 * t); + result += 2.38e-09 * std::cos(2.17720020018 + 155.4203994342 * t); + result += 2.93e-09 * std::cos(4.61501268144 + 4690.4798363586 * t); + result += 2.29e-09 * std::cos(4.7596958807 + 7234.794256242 * t); + result += 2.11e-09 * std::cos(0.21868065485 + 4705.7323075436 * t); + result += 2.01e-09 * std::cos(4.21905743357 + 1349.8674096588 * t); + result += 1.95e-09 * std::cos(4.57808285364 + 529.6909650946 * t); + result += 2.53e-09 * std::cos(2.81496293039 + 1748.016413067 * t); + result += 1.82e-09 * std::cos(5.70454011389 + 6040.3472460174 * t); + result += 1.79e-09 * std::cos(6.02897097053 + 4292.3308329504 * t); + result += 1.86e-09 * std::cos(1.58690991244 + 6309.3741697912 * t); + result += 1.7e-09 * std::cos(2.90220009715 + 9437.762934887 * t); + result += 1.66e-09 * std::cos(1.99984925026 + 8031.0922630584 * t); + result += 1.58e-09 * std::cos(0.04783713552 + 2544.3144198834 * t); + result += 1.97e-09 * std::cos(2.01083639502 + 1194.4470102246 * t); + result += 1.65e-09 * std::cos(5.78372596778 + 83996.8473181119 * t); + result += 2.14e-09 * std::cos(3.38285934319 + 7632.9432596502 * t); + result += 1.4e-09 * std::cos(0.36401486094 + 10447.3878396044 * t); + result += 1.51e-09 * std::cos(0.95153163031 + 6127.6554505572 * t); + result += 1.36e-09 * std::cos(1.48426306582 + 2352.8661537718 * t); + result += 1.27e-09 * std::cos(5.48475435134 + 951.7184062506 * t); + result += 1.26e-09 * std::cos(5.26866506592 + 6279.5527316424 * t); + result += 1.25e-09 * std::cos(3.75754889288 + 6812.766815086 * t); + result += 1.01e-09 * std::cos(4.95015746147 + 398.1490034082 * t); + result += 1.02e-09 * std::cos(0.68468295277 + 1592.5960136328 * t); + result += 1e-09 * std::cos(1.14568935785 + 3894.1818295422 * t); + result += 1.29e-09 * std::cos(0.76540016965 + 553.5694028424 * t); + result += 1.09e-09 * std::cos(5.41063597567 + 6256.7775301916 * t); + result += 7.5e-10 * std::cos(5.84804322893 + 242.728603974 * t); + result += 9.5e-10 * std::cos(1.94452244083 + 11856.2186514245 * t); + result += 7.7e-10 * std::cos(0.69373708195 + 8429.2412664666 * t); + result += 1e-09 * std::cos(5.19725292131 + 244287.600007228 * t); + result += 8e-10 * std::cos(6.18440483705 + 1059.3819301892 * t); + result += 6.9e-10 * std::cos(5.25699888595 + 14143.4952424306 * t); + result += 8.5e-10 * std::cos(5.39484725499 + 25132.3033999656 * t); + result += 6.6e-10 * std::cos(0.51779993906 + 801.8209311238 * t); + result += 5.5e-10 * std::cos(5.16878202461 + 7058.5984613154 * t); + result += 5.1e-10 * std::cos(3.88759155247 + 12036.4607348882 * t); + result += 5e-10 * std::cos(5.57636570536 + 6290.1893969922 * t); + result += 6.1e-10 * std::cos(2.24359003264 + 8635.9420037632 * t); + result += 5e-10 * std::cos(5.54441900966 + 1990.745017041 * t); + result += 5.6e-10 * std::cos(4.0030107804 + 13367.9726311066 * t); + result += 5.2e-10 * std::cos(4.13138898038 + 7860.4193924392 * t); + result += 5.2e-10 * std::cos(3.90943054011 + 26.2983197998 * t); + result += 4.1e-10 * std::cos(3.5712848278 + 7079.3738568078 * t); + result += 5.6e-10 * std::cos(2.76959005761 + 90955.5516944961 * t); + result += 4.2e-10 * std::cos(1.91461189199 + 7477.522860216 * t); + result += 4.2e-10 * std::cos(0.42728171713 + 10213.285546211 * t); + result += 4.2e-10 * std::cos(1.09413724455 + 709.9330485583 * t); + result += 3.9e-10 * std::cos(3.93298068961 + 10973.55568635 * t); + result += 3.8e-10 * std::cos(6.17935925345 + 9917.6968745098 * t); + result += 4.9e-10 * std::cos(0.83021145241 + 11506.7697697936 * t); + result += 5.3e-10 * std::cos(1.45828359397 + 233141.314404362 * t); + result += 4.7e-10 * std::cos(6.21568666789 + 6681.2248533996 * t); + result += 3.7e-10 * std::cos(0.3635930998 + 10177.2576795336 * t); + result += 3.5e-10 * std::cos(3.33024911524 + 5643.1785636774 * t); + result += 3.4e-10 * std::cos(5.63446915337 + 6525.8044539654 * t); + result += 3.5e-10 * std::cos(5.36033855038 + 25158.6017197654 * t); + result += 3.4e-10 * std::cos(5.36319798321 + 4933.2084403326 * t); + result += 3.3e-10 * std::cos(4.24722336872 + 12569.6748183318 * t); + result += 4.3e-10 * std::cos(5.26370903404 + 10575.4066829418 * t); + result += 4.2e-10 * std::cos(5.08837645072 + 11015.1064773348 * t); + result += 4e-10 * std::cos(1.98334703186 + 6284.0561710596 * t); + result += 4.2e-10 * std::cos(4.22496037505 + 88860.0570709867 * t); + result += 2.9e-10 * std::cos(3.1908862817 + 11926.2544136688 * t); + result += 2.9e-10 * std::cos(0.15217616684 + 12168.0026965746 * t); + result += 3e-10 * std::cos(1.61904744136 + 9779.1086761254 * t); + result += 2.7e-10 * std::cos(0.76388991416 + 1589.0728952838 * t); + result += 3.6e-10 * std::cos(2.74712003443 + 3738.761430108 * t); + result += 3.3e-10 * std::cos(3.08807829566 + 3930.2096962196 * t); + result += 3.1e-10 * std::cos(5.34906619513 + 143571.324284816 * t); + result += 2.5e-10 * std::cos(0.10240267494 + 22483.8485744926 * t); + result += 3e-10 * std::cos(3.47110495524 + 14945.3161735544 * t); + result += 2.4e-10 * std::cos(1.10425016019 + 4535.0594369244 * t); + result += 2.4e-10 * std::cos(1.5803725978 + 6496.3749454294 * t); + result += 2.3e-10 * std::cos(3.87710321433 + 6275.9623029906 * t); + result += 2.5e-10 * std::cos(3.9452977897 + 3128.3887650958 * t); + result += 2.3e-10 * std::cos(3.44685609601 + 4136.9104335162 * t); + result += 2.3e-10 * std::cos(3.83156029849 + 5753.3848848968 * t); + result += 2.2e-10 * std::cos(1.86956128067 + 16730.4636895958 * t); + result += 2.5e-10 * std::cos(2.42188933855 + 5729.506447149 * t); + result += 2e-10 * std::cos(1.78208352927 + 17789.845619785 * t); + result += 2.1e-10 * std::cos(4.303630874 + 16858.4825329332 * t); + result += 2.1e-10 * std::cos(0.49258939822 + 29088.811415985 * t); + result += 2.5e-10 * std::cos(1.33030250444 + 6282.0955289232 * t); + result += 2.7e-10 * std::cos(2.54785812264 + 3496.032826134 * t); + result += 2.2e-10 * std::cos(1.1123252195 + 12721.572099417 * t); + result += 2.1e-10 * std::cos(5.97759081637 + 7.1135470008 * t); + result += 1.9e-10 * std::cos(0.80292033311 + 16062.1845261168 * t); + result += 2.3e-10 * std::cos(4.12454848769 + 2388.8940204492 * t); + result += 2.2e-10 * std::cos(4.92663152168 + 18875.525869774 * t); + result += 2.3e-10 * std::cos(5.68902059771 + 16460.333529525 * t); + result += 2.3e-10 * std::cos(4.97346265647 + 17260.1546546904 * t); + result += 2.3e-10 * std::cos(3.03021283729 + 66567.4858652543 * t); + result += 1.6e-10 * std::cos(3.89740925257 + 5331.3574437408 * t); + result += 1.7e-10 * std::cos(3.08268671348 + 154717.609887683 * t); + result += 1.6e-10 * std::cos(3.95085099736 + 3097.88382272579 * t); + result += 1.6e-10 * std::cos(3.99041783945 + 6283.14316029419 * t); + result += 2e-10 * std::cos(6.10644140189 + 167283.761587666 * t); + result += 1.5e-10 * std::cos(4.09775914607 + 11712.9553182308 * t); + result += 1.6e-10 * std::cos(5.717699407 + 17298.1823273262 * t); + result += 1.6e-10 * std::cos(3.28894009404 + 5884.9268465832 * t); + result += 1.5e-10 * std::cos(5.64785377164 + 12559.038152982 * t); + result += 1.6e-10 * std::cos(4.4345208093 + 6283.0085396886 * t); + result += 1.4e-10 * std::cos(2.31721603062 + 5481.2549188676 * t); + result += 1.4e-10 * std::cos(4.43479032305 + 13517.8701062334 * t); + result += 1.4e-10 * std::cos(4.73209312936 + 7342.4577801806 * t); + result += 1.2e-10 * std::cos(0.64705975463 + 18073.7049386502 * t); + result += 1.1e-10 * std::cos(1.514433322 + 16200.7727245012 * t); + result += 1.1e-10 * std::cos(0.88708889185 + 21228.3920235458 * t); + result += 1.4e-10 * std::cos(4.50116508534 + 640.8776073822 * t); + result += 1.1e-10 * std::cos(4.64339996198 + 11790.6290886588 * t); + result += 1.1e-10 * std::cos(1.31064298246 + 4164.311989613 * t); + result += 9e-11 * std::cos(3.02238989305 + 23543.2305046818 * t); + result += 9e-11 * std::cos(2.04999402381 + 22003.9146348698 * t); + result += 9e-11 * std::cos(4.91488110218 + 213.299095438 * t); + return result; +} + +constexpr double getEarthR3(double t) +{ + double result = 0.0; + result += 1.44595e-06 * std::cos(4.27319435148 + 6283.0758499914 * t); + result += 6.729e-08 * std::cos(3.91697608662 + 12566.1516999828 * t); + result += 7.74e-09; + result += 2.47e-09 * std::cos(3.73019298781 + 18849.2275499742 * t); + result += 3.6e-10 * std::cos(2.8008140905 + 6286.5989683404 * t); + result += 3.3e-10 * std::cos(5.62216602775 + 6127.6554505572 * t); + result += 1.9e-10 * std::cos(3.71292621802 + 6438.4962494256 * t); + result += 1.6e-10 * std::cos(4.26011484232 + 6525.8044539654 * t); + result += 1.6e-10 * std::cos(3.50416887054 + 6256.7775301916 * t); + result += 1.4e-10 * std::cos(3.62127621114 + 25132.3033999656 * t); + result += 1.1e-10 * std::cos(4.39200958819 + 4705.7323075436 * t); + result += 1.1e-10 * std::cos(5.22327127059 + 6040.3472460174 * t); + result += 1e-10 * std::cos(4.28045254647 + 83996.8473181119 * t); + result += 9e-11 * std::cos(1.56864096494 + 5507.5532386674 * t); + result += 1.1e-10 * std::cos(1.37795688024 + 6309.3741697912 * t); + result += 1e-10 * std::cos(5.19937959068 + 71430.6956181291 * t); + result += 9e-11 * std::cos(0.4727519993 + 6279.5527316424 * t); + result += 9e-11 * std::cos(0.74642756529 + 5729.506447149 * t); + result += 7e-11 * std::cos(2.9737489156 + 775.522611324 * t); + result += 7e-11 * std::cos(3.28615691021 + 7058.5984613154 * t); + result += 7e-11 * std::cos(2.19184402142 + 6812.766815086 * t); + result += 5e-11 * std::cos(3.15419034438 + 529.6909650946 * t); + result += 6e-11 * std::cos(4.54725567047 + 1059.3819301892 * t); + result += 5e-11 * std::cos(1.51104406936 + 7079.3738568078 * t); + result += 7e-11 * std::cos(2.98052059053 + 6681.2248533996 * t); + result += 5e-11 * std::cos(2.30961231391 + 12036.4607348882 * t); + result += 5e-11 * std::cos(3.71102966917 + 6290.1893969922 * t); + return result; +} + +constexpr double getEarthR4(double t) +{ + double result = 0.0; + result += 3.858e-08 * std::cos(2.56384387339 + 6283.0758499914 * t); + result += 3.06e-09 * std::cos(2.2676950123 + 12566.1516999828 * t); + result += 5.3e-10 * std::cos(3.44031471924 + 5573.1428014331 * t); + result += 1.5e-10 * std::cos(2.04794573436 + 18849.2275499742 * t); + result += 1.3e-10 * std::cos(2.05688873673 + 77713.7714681205 * t); + result += 7e-11 * std::cos(4.4121885448 + 161000.685737674 * t); + result += 5e-11 * std::cos(5.26154653107 + 6438.4962494256 * t); + result += 5e-11 * std::cos(4.07695126049 + 6127.6554505572 * t); + result += 6e-11 * std::cos(3.81514213664 + 149854.400134808 * t); + result += 3e-11 * std::cos(1.28175749811 + 6286.5989683404 * t); + return result; +} + +constexpr double getEarthR5(double t) +{ + double result = 0.0; + result += 8.6e-10 * std::cos(1.21579741687 + 6283.0758499914 * t); + result += 1.2e-10 * std::cos(0.65617264033 + 12566.1516999828 * t); + result += 1e-11 * std::cos(0.38068797142 + 18849.2275499742 * t); + return result; +} + +constexpr double getJulianThousandYears(double jd) +{ + constexpr double DaysOf1000Years = 365.25 * 1000; + return (jd - J2000) / DaysOf1000Years; +} + +constexpr double mod2Pi(double r) +{ + while (r < 0) { + r += std::numbers::pi * 2; + } + while (r > 2 * std::numbers::pi) { + r -= std::numbers::pi * 2; + } + return r; +} + +/** + * Calculates the Heliocentric Yellow Meridian (rad) of the Earth by Julian day + */ +constexpr double getSunEclipticLongitudeForEarth(double julianDay) +{ + const double t = getJulianThousandYears(julianDay); + const double L0 = getEarthL0(t); + const double L1 = getEarthL1(t); + const double L2 = getEarthL2(t); + const double L3 = getEarthL3(t); + const double L4 = getEarthL4(t); + const double L5 = getEarthL5(t); + const double L = ((((L5 * t + L4) * t + L3) * t + L2) * t + L1) * t + L0; + return mod2Pi(L); +} + +constexpr double getSunEclipticLatitudeForEarth(double jd) +{ + const double t = getJulianThousandYears(jd); + const double B0 = getEarthB0(t); + const double B1 = getEarthB1(t); + const double B2 = getEarthB2(t); + const double B3 = getEarthB3(t); + const double B4 = getEarthB4(t); + const double B = ((((B4 * t) + B3) * t + B2) * t + B1) * t + B0; + return B; +} + +constexpr double getJulianCentury(double julianDay) +{ + // days of 100 years + constexpr double DaysOfCentury = 365.25 * 100; + return (julianDay - J2000) / DaysOfCentury; +} + +constexpr double secondsToDegrees(double seconds) +{ + return seconds / 3600; +} + +constexpr double degreesToRadians(double degrees) +{ + return degrees * std::numbers::pi / 180; +} + +constexpr double secondsToRadians(double seconds) +{ + return degreesToRadians(secondsToDegrees(seconds)); +} + +consteval double coefficient() +{ + return secondsToRadians(0.0001); +} + +void getEarthNutationParameter(EarthNutationParameter &earthNutationParameter, double T) +{ + const double T2 = T * T; + const double T3 = T2 * T; + + earthNutationParameter.D = degreesToRadians(297.85036 + 445267.111480 * T - 0.0019142 * T2 + T3 / 189474.0); + earthNutationParameter.M = degreesToRadians(357.52772 + 35999.050340 * T - 0.0001603 * T2 - T3 / 300000.0); + earthNutationParameter.Mp = degreesToRadians(134.96298 + 477198.867398 * T + 0.0086972 * T2 + T3 / 56250.0); + earthNutationParameter.F = degreesToRadians(93.27191 + 483202.017538 * T - 0.0036825 * T2 + T3 / 327270.0); + earthNutationParameter.Omega = degreesToRadians(125.04452 - 1934.136261 * T + 0.0020708 * T2 + T3 / 450000.0); +} + +double calcEarthLongitudeNutation(double T) +{ + EarthNutationParameter radian; + getEarthNutationParameter(radian, T); + double result = 0.0; + for (std::size_t i = 0; i < s_nuation.size(); i++) { + double theta = + s_nuation[i].D * radian.D + s_nuation[i].M * radian.M + s_nuation[i].Mp * radian.Mp + s_nuation[i].F * radian.F + s_nuation[i].Omega * radian.Omega; + result += (s_nuation[i].Sine1 + s_nuation[i].Sine2 * T) * std::sin(theta); + } + return result * coefficient(); +} + +constexpr double Vsop2Fk5LongitudeCorrection(double latitude, double longitude, double julianDay) +{ + const double t = getJulianCentury(julianDay); + const double lp = latitude - degreesToRadians(1.397) * t - degreesToRadians(0.00031) * t * t; + return secondsToRadians(-0.09033 + 0.03916 * (std::cos(lp) + std::sin(lp)) * std::tan(longitude)); +} + +/** + * Calculates ecliptical longitude of earth in heliocentric coordinates based on VSOP87D table + * @see https://ftp.imcce.fr/pub/ephem/planets/vsop87/VSOP87D.ear + * @return earth longitude in radians + */ +constexpr double getSunRadiusForEarth(double julianDay) +{ + const double t = getJulianThousandYears(julianDay); + const double R0 = getEarthR0(t); + const double R1 = getEarthR1(t); + const double R2 = getEarthR2(t); + const double R3 = getEarthR3(t); + const double R4 = getEarthR4(t); + const double R5 = getEarthR5(t); + const double R = ((((R5 * t + R4) * t + R3) * t + R2) * t + R1) * t + R0; + return R; +} + +double getEarthEclipticLongitudeForSun(double jd) +{ + double l = getSunEclipticLongitudeForEarth(jd); + const double b = getSunEclipticLatitudeForEarth(jd); + l += calcEarthLongitudeNutation(getJulianCentury(jd)); + l += Vsop2Fk5LongitudeCorrection(l, b, jd); + l = mod2Pi(l + std::numbers::pi); + // Light-time correction depends upon the velocity and distance of the emitting object during the time it takes for its light to travel to Earth. + // 20.49522″ = arctan(earth revolution linear velocity / view distance between earth and sun) + // FIXME: why divided by r + const double r = getSunRadiusForEarth(jd); + l -= secondsToRadians(20.4898) / r; + return l; +} + +void getMoonEclipticParameter(MoonEclipticParameter &moonEclipticParameter, double T) +{ + double T2 = T * T; + double T3 = T2 * T; + double T4 = T3 * T; + + moonEclipticParameter.Lp = mod2Pi(degreesToRadians(218.3164591 + 481267.88134236 * T - 0.0013268 * T2 + T3 / 538841.0 - T4 / 65194000.0)); + moonEclipticParameter.D = mod2Pi(degreesToRadians(297.8502042 + 445267.1115168 * T - 0.0016300 * T2 + T3 / 545868.0 - T4 / 113065000.0)); + moonEclipticParameter.M = mod2Pi(degreesToRadians(357.5291092 + 35999.0502909 * T - 0.0001536 * T2 + T3 / 24490000.0)); + moonEclipticParameter.Mp = mod2Pi(degreesToRadians(134.9634114 + 477198.8676313 * T + 0.0089970 * T2 + T3 / 69699.0 - T4 / 14712000.0)); + moonEclipticParameter.F = mod2Pi(degreesToRadians(93.2720993 + 483202.0175273 * T - 0.0034029 * T2 - T3 / 3526000.0 + T4 / 863310000.0)); + moonEclipticParameter.E = 1 - 0.002516 * T - 0.0000074 * T2; +} + +double calcMoonECLongitudePeriodic(MoonEclipticParameter &moonEclipticParameter) +{ + double EI = 0.0; + for (std::size_t i = 0; i < s_moonLongitude.size(); i++) { + double theta = s_moonLongitude[i].D * moonEclipticParameter.D + s_moonLongitude[i].M * moonEclipticParameter.M + + s_moonLongitude[i].Mp * moonEclipticParameter.Mp + s_moonLongitude[i].F * moonEclipticParameter.F; + EI += s_moonLongitude[i].EiA * std::sin(theta) * std::pow(moonEclipticParameter.E, std::abs(s_moonLongitude[i].M)); + } + return EI; +} + +double calcMoonLongitudePerturbation(double T, const MoonEclipticParameter &moonEclipticParameter) +{ + const double A1 = mod2Pi(degreesToRadians(119.75 + 131.849 * T)); + const double A2 = mod2Pi(degreesToRadians(53.09 + 479264.290 * T)); + + return 3958.0 * std::sin(A1) + 1962.0 * std::sin(moonEclipticParameter.Lp - moonEclipticParameter.F) + 318.0 * std::sin(A2); +} + +double getMoonEclipticLongitudeEC(double julianDay) +{ + MoonEclipticParameter radian; + double T = getJulianCentury(julianDay); + getMoonEclipticParameter(radian, T); + double EI = calcMoonECLongitudePeriodic(radian); + EI += calcMoonLongitudePerturbation(T, radian); + double longitude = radian.Lp + degreesToRadians(EI / 1000000.0); + longitude += calcEarthLongitudeNutation(T); + return longitude; +} + +double NewtonIteration(double angle, double x0) +{ + constexpr double EPSILON = 1e-7; + constexpr double DELTA = 5e-6; + // Make sure the rad value is in between -PI and PI + auto func = [angle](double x) -> double { + double r = getEarthEclipticLongitudeForSun(x) - angle; + while (r < -std::numbers::pi) { + r += std::numbers::pi * 2; + } + while (r > std::numbers::pi) { + r -= std::numbers::pi * 2; + } + return r; + }; + + double x; + unsigned count = 0; + while (count++ < 100u) { + x = x0; + double fx, fpx; + fx = func(x); + // derivative + fpx = (func(x + DELTA) - func(x - DELTA)) / DELTA / 2; + x0 = x - fx / fpx; + if (std::abs(x0 - x) <= EPSILON) { + break; + } + } + return x; +} + +// From http://eclipse.gsfc.nasa.gov/SEhelp/deltatpoly2004.html +double getDeltaT(int year, int month) +{ + double y = double(year) + (double(month) - 0.5) / 12; + + if (year < -500) { + double u = (double(year) - 1820) / 100; + return -20 + 32 * u * u; + } else if (year < 500) { + double u = y / 100; + double u2 = u * u; + double u3 = u2 * u; + double u4 = u3 * u; + double u5 = u4 * u; + double u6 = u5 * u; + return 10583.6 - 1014.41 * u + 33.78311 * u2 - 5.952053 * u3 - 0.1798452 * u4 + 0.022174192 * u5 + 0.0090316521 * u6; + } else if (year < 1600) { + double u = (y - 1000) / 100; + double u2 = u * u; + double u3 = u2 * u; + double u4 = u3 * u; + double u5 = u4 * u; + double u6 = u5 * u; + return 1574.2 - 556.01 * u + 71.23472 * u2 + 0.319781 * u3 - 0.8503463 * u4 - 0.005050998 * u5 + 0.0083572073 * u6; + } else if (year < 1700) { + double t = y - 1600; + double t2 = t * t; + double t3 = t2 * t; + return 120 - 0.9808 * t - 0.01532 * t2 + t3 / 7129; + } else if (year < 1800) { + double t = y - 1700; + double t2 = t * t; + double t3 = t2 * t; + double t4 = t3 * t; + return 8.83 + 0.1603 * t - 0.0059285 * t2 + 0.00013336 * t3 - t4 / 1174000; + } else if (year < 1860) { + double t = y - 1800; + double t2 = t * t; + double t3 = t2 * t; + double t4 = t3 * t; + double t5 = t4 * t; + double t6 = t5 * t; + double t7 = t6 * t; + return 13.72 - 0.332447 * t + 0.0068612 * t2 + 0.0041116 * t3 - 0.00037436 * t4 + 0.0000121272 * t5 - 0.0000001699 * t6 + 0.000000000875 * t7; + } else if (year < 1900) { + double t = y - 1860; + double t2 = t * t; + double t3 = t2 * t; + double t4 = t3 * t; + double t5 = t4 * t; + return 7.62 + 0.5737 * t - 0.251754 * t2 + 0.01680668 * t3 - 0.0004473624 * t4 + t5 / 233174; + } else if (year < 1920) { + double t = y - 1900; + double t2 = t * t; + double t3 = t2 * t; + double t4 = t3 * t; + return -2.79 + 1.494119 * t - 0.0598939 * t2 + 0.0061966 * t3 - 0.000197 * t4; + } else if (year < 1941) { + double t = y - 1920; + double t2 = t * t; + double t3 = t2 * t; + return 21.20 + 0.84493 * t - 0.076100 * t2 + 0.0020936 * t3; + } else if (year < 1961) { + double t = y - 1950; + double t2 = t * t; + double t3 = t2 * t; + return 29.07 + 0.407 * t - t2 / 233 + t3 / 2547; + } else if (year < 1986) { + double t = y - 1975; + double t2 = t * t; + double t3 = t2 * t; + return 45.45 + 1.067 * t - t2 / 260 - t3 / 718; + } else if (year < 2005) { + double t = y - 2000; + double t2 = t * t; + double t3 = t2 * t; + double t4 = t3 * t; + double t5 = t4 * t; + return 63.86 + 0.3345 * t - 0.060374 * t2 + 0.0017275 * t3 + 0.000651814 * t4 + 0.00002373599 * t5; + } else if (year < 2050) { + double t = y - 2000; + double t2 = t * t; + return 62.92 + 0.32217 * t + 0.005589 * t2; + } else if (year < 2150) { + double u = (y - 1820) / 100; + double u2 = u * u; + return -20 + 32 * u2 - 0.5628 * (2150 - y); + } else { + double u = (y - 1820) / 100; + double u2 = u * u; + return -20 + 32 * u2; + } +} + +int64_t toJulianDay(int year, int month, int day) +{ +#if __has_cpp_attribute(assume) + [[assume(year > 0 && day > 0)]]; + [[assume(month > 0 && month <= 12)]]; +#endif + int a = (14 - month) / 12; + int y = year + 4800 - a; + int m = month + 12 * a - 3; + return day + (153 * m + 2) / 5 + 365 * y + y / 4 - y / 100 + y / 400 - 32045; +} + +void getDateFromJulianDay(double julianDay, int &yy, int &mm, int &dd) +{ + /* + * This algorithm is taken from + * "Numerical Recipes in c, 2nd Ed." (1992), pp. 14-15 + * and converted to integer math. + * The electronic version of the book is freely available + * at http://www.nr.com/ , under "Obsolete Versions - Older + * book and code versions. + */ + constexpr int64_t JD_GREG_CAL = 2299161; + constexpr int64_t JB_MAX_WITHOUT_OVERFLOW = 107374182; + int64_t julian = int64_t(std::floor(julianDay + 0.5)); + + int64_t ta, jalpha, tb, tc, td, te; + + if (julian >= JD_GREG_CAL) { + jalpha = (4 * (julian - 1867216) - 1) / 146097; + ta = julian + 1 + jalpha - jalpha / 4; + } else if (julian < 0) { + ta = julian + 36525 * (1 - julian / 36525); + } else { + ta = julian; + } + + tb = ta + 1524; + if (tb <= JB_MAX_WITHOUT_OVERFLOW) { + tc = (tb * 20 - 2442) / 7305; + } else { + tc = int64_t((uint64_t(tb) * 20 - 2442) / 7305); + } + + td = 365 * tc + tc / 4; + te = ((tb - td) * 10000) / 306001; + dd = int(tb - td - (306001 * te) / 10000); + mm = int(te - 1); + + if (mm > 12) { + mm -= 12; + } + yy = int(tc - 4715); + if (mm > 2) { + yy--; + } + if (julian < 0) { + yy -= int(100 * (1 - julian / 36525)); + } +} +} \ No newline at end of file diff --git a/plasmacalendarplugins/astronomical/CMakeLists.txt b/plasmacalendarplugins/astronomical/CMakeLists.txt new file mode 100644 index 00000000..a5216620 --- /dev/null +++ b/plasmacalendarplugins/astronomical/CMakeLists.txt @@ -0,0 +1,9 @@ +kcoreaddons_add_plugin(astronomicalevents SOURCES astronomicaleventsplugin.cpp INSTALL_NAMESPACE "plasmacalendarplugins") +target_link_libraries(astronomicalevents + KF6::ConfigCore + KF6::Holidays + KF6::CalendarEvents + KF6::I18n +) + +add_subdirectory(config) diff --git a/plasmacalendarplugins/astronomical/Messages.sh b/plasmacalendarplugins/astronomical/Messages.sh new file mode 100644 index 00000000..e242b1bb --- /dev/null +++ b/plasmacalendarplugins/astronomical/Messages.sh @@ -0,0 +1,4 @@ +#! /usr/bin/env bash +$EXTRACTRC `find . -name \*.rc -o -name \*.ui -o -name \*.kcfg` >> rc.cpp +$XGETTEXT `find . -name \*.js -o -name \*.qml -o -name \*.cpp` -o $podir/plasma_calendar_astronomicalevents.pot +rm -f rc.cpp diff --git a/plasmacalendarplugins/astronomical/astronomicaleventsplugin.cpp b/plasmacalendarplugins/astronomical/astronomicaleventsplugin.cpp new file mode 100644 index 00000000..dbf7f88c --- /dev/null +++ b/plasmacalendarplugins/astronomical/astronomicaleventsplugin.cpp @@ -0,0 +1,71 @@ +/* + SPDX-FileCopyrightText: 2018 Friedrich W. H. Kossebau + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "astronomicaleventsplugin.h" + +// KF +#include +#include +#include +#include +#include +// Qt +#include + +AstronomicalEventsPlugin::AstronomicalEventsPlugin() + : CalendarEvents::CalendarEventsPlugin() +{ + auto config = KSharedConfig::openConfig(QStringLiteral("plasma_calendar_astronomicalevents")); + const KConfigGroup generalConfig = config->group("General"); + + m_lunarPhaseShown = generalConfig.readEntry("showLunarPhase", true); + m_seasonShown = generalConfig.readEntry("showSeason", true); +} + +AstronomicalEventsPlugin::~AstronomicalEventsPlugin() +{ +} + +void AstronomicalEventsPlugin::loadEventsForDateRange(const QDate &startDate, const QDate &endDate) +{ + QMultiHash data; + + if (!endDate.isValid()) { + Q_EMIT dataReady(data); + return; + } + + for (QDate date = startDate; date <= endDate && date.isValid(); date = date.addDays(1)) { + if (m_lunarPhaseShown) { + const auto phase = KHolidays::LunarPhase::phaseAtDate(date); + if (phase == KHolidays::LunarPhase::NewMoon || phase == KHolidays::LunarPhase::FirstQuarter || phase == KHolidays::LunarPhase::LastQuarter + || phase == KHolidays::LunarPhase::FullMoon) { + CalendarEvents::EventData lunarPhaseData; + lunarPhaseData.setIsAllDay(true); + lunarPhaseData.setTitle(KHolidays::LunarPhase::phaseName(phase)); + lunarPhaseData.setEventType(CalendarEvents::EventData::Event); + lunarPhaseData.setIsMinor(false); + + data.insert(date, lunarPhaseData); + } + } + + if (m_seasonShown) { + const auto season = KHolidays::AstroSeasons::seasonAtDate(date); + if (season != KHolidays::AstroSeasons::None) { + CalendarEvents::EventData seasonData; + seasonData.setIsAllDay(true); + seasonData.setTitle(KHolidays::AstroSeasons::seasonName(season)); + seasonData.setEventType(CalendarEvents::EventData::Event); + seasonData.setIsMinor(false); + + data.insert(date, seasonData); + } + } + } + + Q_EMIT dataReady(data); +} diff --git a/plasmacalendarplugins/astronomical/astronomicaleventsplugin.h b/plasmacalendarplugins/astronomical/astronomicaleventsplugin.h new file mode 100644 index 00000000..4ae1760f --- /dev/null +++ b/plasmacalendarplugins/astronomical/astronomicaleventsplugin.h @@ -0,0 +1,30 @@ +/* + SPDX-FileCopyrightText: 2018 Friedrich W. H. Kossebau + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef ASTRONOMICALEVENTSPLUGIN_H +#define ASTRONOMICALEVENTSPLUGIN_H + +// KF +#include + +class AstronomicalEventsPlugin : public CalendarEvents::CalendarEventsPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.kde.CalendarEventsPlugin" FILE "astronomicaleventsplugin.json") + Q_INTERFACES(CalendarEvents::CalendarEventsPlugin) + +public: + AstronomicalEventsPlugin(); + ~AstronomicalEventsPlugin() override; + + void loadEventsForDateRange(const QDate &startDate, const QDate &endDate) override; + +private: + bool m_lunarPhaseShown; + bool m_seasonShown; +}; + +#endif diff --git a/plasmacalendarplugins/astronomical/astronomicaleventsplugin.json b/plasmacalendarplugins/astronomical/astronomicaleventsplugin.json new file mode 100644 index 00000000..4382f0b8 --- /dev/null +++ b/plasmacalendarplugins/astronomical/astronomicaleventsplugin.json @@ -0,0 +1,141 @@ +{ + "KPlugin": { + "Authors": [ + { + "Email": "kossebau@kde.org", + "Name": "Friedrich W. H. Kossebau", + "Name[ar]": "فريدريش دبليو اتش كوسباو", + "Name[az]": "Friedrich W. H. Kossebau", + "Name[bg]": "Friedrich W. H. Kossebau", + "Name[ca@valencia]": "Friedrich W. H. Kossebau", + "Name[ca]": "Friedrich W. H. Kossebau", + "Name[cs]": "Friedrich W. H. Kossebau", + "Name[da]": "Friedrich W. H. Kossebau", + "Name[de]": "Friedrich W. H. Kossebau", + "Name[en_GB]": "Friedrich W. H. Kossebau", + "Name[eo]": "Friedrich W. H. Kossebau", + "Name[es]": "Friedrich W. H. Kossebau", + "Name[eu]": "Friedrich W. H. Kossebau", + "Name[fi]": "Friedrich W. H. Kossebau", + "Name[fr]": "Friedrich W. H. Kossebau", + "Name[gl]": "Friedrich W. H. Kossebau", + "Name[he]": "פרידריך ו. ה. קוסבאו", + "Name[hu]": "Friedrich W. H. Kossebau", + "Name[ia]": "Friedrich W. H. Kossebau", + "Name[id]": "Friedrich W. H. Kossebau", + "Name[is]": "Friedrich W. H. Kossebau", + "Name[it]": "Friedrich W. H. Kossebau", + "Name[ja]": "Friedrich W. H. Kossebau", + "Name[ka]": "Friedrich W. H. Kossebau", + "Name[ko]": "Friedrich W. H. Kossebau", + "Name[lt]": "Friedrich W. H. Kossebau", + "Name[lv]": "Friedrich W. H. Kossebau", + "Name[nl]": "Friedrich W. H. Kossebau", + "Name[nn]": "Friedrich W.H. Kossebau", + "Name[pl]": "Friedrich W. H. Kossebau", + "Name[pt]": "Friedrich W. H. Kossebau", + "Name[pt_BR]": "Friedrich W. H. Kossebau", + "Name[ro]": "Friedrich W. H. Kossebau", + "Name[ru]": "Friedrich W. H. Kossebau", + "Name[sk]": "Friedrich W. H. Kossebau", + "Name[sl]": "Friedrich W. H. Kossebau", + "Name[sv]": "Friedrich W. H. Kossebau", + "Name[tr]": "Friedrich W. H. Kossebau", + "Name[uk]": "Friedrich W. H. Kossebau", + "Name[vi]": "Friedrich W. H. Kossebau", + "Name[x-test]": "xxFriedrich W. H. Kossebauxx", + "Name[zh_CN]": "Friedrich W. H. Kossebau", + "Name[zh_TW]": "Friedrich W. H. Kossebau" + } + ], + "Description": "Calendar plugin for displaying astronomical events", + "Description[ar]": "ملحق لتقويم يعرض الأحداث الفلكية", + "Description[az]": "Astronomik hadisələri göstərmək üçün təqvim əlavəsi", + "Description[bg]": "Приставка на календара за показване на астрономични събития", + "Description[ca@valencia]": "Connector de calendari per a mostrar esdeveniments astronòmics", + "Description[ca]": "Connector de calendari per a mostrar esdeveniments astronòmics", + "Description[cs]": "Modul kalendáře pro zobrazování astronomických událostí", + "Description[da]": "Kalenderplugin, der viser astronomiske begivenheder", + "Description[de]": "Kalender-Modul zur Anzeige von astronomischen Ereignissen", + "Description[en_GB]": "Calendar plugin for displaying astronomical events", + "Description[eo]": "Kalendara kromaĵo por montri astronomiajn eventojn", + "Description[es]": "Complemento del calendario para mostrar eventos astronómicos", + "Description[eu]": "Gertaera astronomikoak erakusteko egutegiko plugina", + "Description[fi]": "Kalenteriliitännäisen tähtitieteen tapahtumien näyttämiseksi", + "Description[fr]": "Module externe de calendrier pour l'affichage des évènements astronomiques", + "Description[gl]": "Complemento de calendario para amosar sucesos astronómicos", + "Description[he]": "תוסף לוח שנה להצגת אירועים אסטרונומיים", + "Description[hu]": "Naptár bővítmény csillagászati események megjelenítéséhez", + "Description[ia]": "Plugin de calendario per monstrar eventos astronomic", + "Description[id]": "Plugin kalender untuk menampilkan peristiwa astronomis", + "Description[is]": "Dagatalsviðbót til að birta stjarnfræðilega atburði", + "Description[it]": "Estensione del calendario per visualizzare gli eventi astronomici", + "Description[ja]": "天文現象を表示するカレンダープラグイン", + "Description[ka]": "კალენდარის დამატება ასტრონომიული მოვლენების საჩვენებლად", + "Description[ko]": "천문 현상을 표시하는 달력 플러그인", + "Description[lt]": "Kalendoriaus įskiepis, skirtas astronominių įvykių rodymui", + "Description[lv]": "Kalendāra spraudnis, lai parādītu astronomiskus notikumus", + "Description[nl]": "Plug-in voor agenda voor tonen van astronomische gebeurtenissen", + "Description[nn]": "Kalendertillegg for vising av astronomiske hendingar", + "Description[pl]": "Wtyczka kalendarza do wyświetlania wydarzeń astronomicznych", + "Description[pt]": "'Plugin' de calendário para mostrar os eventos astronómicos", + "Description[pt_BR]": "Plugin de calendário para mostrar eventos astronômicos", + "Description[ro]": "Extensie de calendar pentru afișarea evenimentelor astronomice", + "Description[ru]": "Модуль календаря для отображения астрономических событий", + "Description[sk]": "Zásuvný modul kalendára na zobrazovanie astronomických udalostí", + "Description[sl]": "Koledarski vtičnik za prikaz astronomskih dogodkov", + "Description[sv]": "Kalenderinsticksprogram för att visa astronomiska händelser", + "Description[tr]": "Gökbilimsel olayları görüntülemek için takvim eklentisi", + "Description[uk]": "Додаток календаря для показу астрономічних подій", + "Description[vi]": "Phần cài cắm lịch để hiển thị các sự kiện thiên văn", + "Description[x-test]": "xxCalendar plugin for displaying astronomical eventsxx", + "Description[zh_CN]": "显示天文事件的日历插件", + "Description[zh_TW]": "用於顯示天文事件的行事曆外掛程式", + "Icon": "weather-clear-night", + "Name": "Astronomical Events", + "Name[ar]": "أحداث فلكية", + "Name[ast]": "Eventos astronómicos", + "Name[az]": "Astronomik hadisələr", + "Name[bg]": "Астрономически събития", + "Name[ca@valencia]": "Esdeveniments astronòmics", + "Name[ca]": "Esdeveniments astronòmics", + "Name[cs]": "Astronomické události", + "Name[da]": "Astronomiske begivenheder", + "Name[de]": "Astronomische Ereignisse", + "Name[en_GB]": "Astronomical Events", + "Name[eo]": "Astronomiaj Eventoj", + "Name[es]": "Eventos astronómicos", + "Name[eu]": "Gertaera astronomikoak", + "Name[fi]": "Tähtitieteen tapahtumat", + "Name[fr]": "Évènements astronomiques", + "Name[gl]": "Sucesos astronómicos", + "Name[he]": "אירועים אסטרונומיים", + "Name[hu]": "Csillagászati események", + "Name[ia]": "Eventos astronomic", + "Name[id]": "Peristiwa Astronomis", + "Name[is]": "Stjarnfræðilegir atburðir", + "Name[it]": "Eventi astronomici", + "Name[ja]": "天文現象", + "Name[ka]": "ასტრონომიული მოვლენები", + "Name[ko]": "천문 현상", + "Name[lt]": "Astronominiai įvykiai", + "Name[lv]": "Astronomiskie notikumi", + "Name[nl]": "Astronomische gebeurtenissen", + "Name[nn]": "Astronomiske hendingar", + "Name[pl]": "Wydarzenia astronomiczne", + "Name[pt]": "Eventos Astronómicos", + "Name[pt_BR]": "Eventos astronômicos", + "Name[ro]": "Evenimente astronomice", + "Name[ru]": "Астрономические события", + "Name[sk]": "Astronomické udalosti", + "Name[sl]": "Astronomski dogodki", + "Name[sv]": "Astronomiska händelser", + "Name[tr]": "Gökbilimsel Olaylar", + "Name[uk]": "Астрономічні події", + "Name[vi]": "Các sự kiện thiên văn", + "Name[x-test]": "xxAstronomical Eventsxx", + "Name[zh_CN]": "天文事件", + "Name[zh_TW]": "天文事件" + }, + "X-KDE-PlasmaCalendar-ConfigUi": "astronomicalevents/AstronomicalEventsConfig.qml" +} diff --git a/plasmacalendarplugins/astronomical/config/CMakeLists.txt b/plasmacalendarplugins/astronomical/config/CMakeLists.txt new file mode 100644 index 00000000..d81749c8 --- /dev/null +++ b/plasmacalendarplugins/astronomical/config/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(qml) +add_subdirectory(plugin) diff --git a/plasmacalendarplugins/astronomical/config/plugin/CMakeLists.txt b/plasmacalendarplugins/astronomical/config/plugin/CMakeLists.txt new file mode 100644 index 00000000..9dc0c694 --- /dev/null +++ b/plasmacalendarplugins/astronomical/config/plugin/CMakeLists.txt @@ -0,0 +1,8 @@ +ecm_add_qml_module(plasmacalendarastronomicaleventsconfig URI org.kde.plasmacalendar.astronomicaleventsconfig) +target_sources(plasmacalendarastronomicaleventsconfig PRIVATE configplugin.cpp configstorage.cpp) +target_link_libraries(plasmacalendarastronomicaleventsconfig PRIVATE + Qt::Qml + Qt::Core + KF6::ConfigCore +) +ecm_finalize_qml_module(plasmacalendarastronomicaleventsconfig) diff --git a/plasmacalendarplugins/astronomical/config/plugin/configplugin.cpp b/plasmacalendarplugins/astronomical/config/plugin/configplugin.cpp new file mode 100644 index 00000000..c7399caa --- /dev/null +++ b/plasmacalendarplugins/astronomical/config/plugin/configplugin.cpp @@ -0,0 +1,24 @@ +/* + SPDX-FileCopyrightText: 2018 Friedrich W. H. Kossebau + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "configstorage.h" + +#include +#include + +class AstronomicalConfigPlugin : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") + +public: + void registerTypes(const char *uri) override + { + qmlRegisterType(uri, 1, 0, "ConfigStorage"); + } +}; + +#include "configplugin.moc" diff --git a/plasmacalendarplugins/astronomical/config/plugin/configstorage.cpp b/plasmacalendarplugins/astronomical/config/plugin/configstorage.cpp new file mode 100644 index 00000000..231fccb8 --- /dev/null +++ b/plasmacalendarplugins/astronomical/config/plugin/configstorage.cpp @@ -0,0 +1,28 @@ +/* + SPDX-FileCopyrightText: 2018 Friedrich W. H. Kossebau + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "configstorage.h" + +// KF +#include + +ConfigStorage::ConfigStorage(QObject *parent) + : QObject(parent) +{ + auto config = KSharedConfig::openConfig(QStringLiteral("plasma_calendar_astronomicalevents")); + m_generalConfigGroup = config->group("General"); + + m_lunarPhaseShown = m_generalConfigGroup.readEntry("showLunarPhase", true); + m_seasonShown = m_generalConfigGroup.readEntry("showSeason", true); +} + +void ConfigStorage::save() +{ + m_generalConfigGroup.writeEntry("showLunarPhase", m_lunarPhaseShown); + m_generalConfigGroup.writeEntry("showSeason", m_seasonShown); + + m_generalConfigGroup.sync(); +} diff --git a/plasmacalendarplugins/astronomical/config/plugin/configstorage.h b/plasmacalendarplugins/astronomical/config/plugin/configstorage.h new file mode 100644 index 00000000..15924c27 --- /dev/null +++ b/plasmacalendarplugins/astronomical/config/plugin/configstorage.h @@ -0,0 +1,36 @@ +/* + SPDX-FileCopyrightText: 2018 Friedrich W. H. Kossebau + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef CONFIGSTORAGE_H +#define CONFIGSTORAGE_H + +// KF +#include +#include + +class ConfigStorage : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool isLunarPhaseShown MEMBER m_lunarPhaseShown NOTIFY lunarPhaseShownChanged) + Q_PROPERTY(bool isSeasonShown MEMBER m_seasonShown NOTIFY seasonShownChanged) + +public: + explicit ConfigStorage(QObject *parent = nullptr); + + Q_INVOKABLE void save(); + +Q_SIGNALS: + void lunarPhaseShownChanged(); + void seasonShownChanged(); + +private: + KConfigGroup m_generalConfigGroup; + + bool m_lunarPhaseShown; + bool m_seasonShown; +}; + +#endif diff --git a/plasmacalendarplugins/astronomical/config/qml/AstronomicalEventsConfig.qml b/plasmacalendarplugins/astronomical/config/qml/AstronomicalEventsConfig.qml new file mode 100644 index 00000000..d2e6b133 --- /dev/null +++ b/plasmacalendarplugins/astronomical/config/qml/AstronomicalEventsConfig.qml @@ -0,0 +1,54 @@ +/* + SPDX-FileCopyrightText: 2018 Friedrich W. H. Kossebau + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +import QtQuick 2.5 +import QtQuick.Controls 2.5 as QQC2 +import QtQuick.Layouts 1.3 + +import org.kde.plasmacalendar.astronomicaleventsconfig 1.0 +import org.kde.kirigami 2.5 as Kirigami +import org.kde.kcmutils as KCM + +KCM.SimpleKCM { + id: configPage + + // expected API + signal configurationChanged + + // expected API + function saveConfig() + { + configStorage.isLunarPhaseShown = showLunarPhasesCheckBox.checked; + configStorage.isSeasonShown = showSeasonsCheckBox.checked; + + configStorage.save(); + } + + Kirigami.FormLayout { + + ConfigStorage { + id: configStorage + } + + QQC2.CheckBox { + id: showLunarPhasesCheckBox + + Kirigami.FormData.label: i18nd("plasma_calendar_astronomicalevents", "Show:") + + checked: configStorage.isLunarPhaseShown + text: i18ndc("plasma_calendar_astronomicalevents", "@option:check", "Lunar phases") + onCheckedChanged: configPage.configurationChanged(); + } + + QQC2.CheckBox { + id: showSeasonsCheckBox + + checked: configStorage.isSeasonShown + text: i18ndc("plasma_calendar_astronomicalevents", "@option:check", "Astronomical seasons (solstices and equinoxes)") + onCheckedChanged: configPage.configurationChanged(); + } + } +} diff --git a/plasmacalendarplugins/astronomical/config/qml/CMakeLists.txt b/plasmacalendarplugins/astronomical/config/qml/CMakeLists.txt new file mode 100644 index 00000000..dd66b65a --- /dev/null +++ b/plasmacalendarplugins/astronomical/config/qml/CMakeLists.txt @@ -0,0 +1 @@ +install(FILES AstronomicalEventsConfig.qml DESTINATION ${KDE_INSTALL_PLUGINDIR}/plasmacalendarplugins/astronomicalevents) diff --git a/po/ar/kwin_effect_cube.po b/po/ar/kwin_effect_cube.po new file mode 100644 index 00000000..4d717b83 --- /dev/null +++ b/po/ar/kwin_effect_cube.po @@ -0,0 +1,56 @@ +# Copyright (C) 2024 This file is copyright: +# This file is distributed under the same license as the kdeplasma-addons package. +# +# SPDX-FileCopyrightText: 2024 Zayed Al-Saidi +msgid "" +msgstr "" +"Project-Id-Version: kdeplasma-addons\n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2024-06-22 00:40+0000\n" +"PO-Revision-Date: 2024-03-30 10:55+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: ar\n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" + +#: kcm/cubeeffectkcm.cpp:35 +#, kde-format +msgid "KWin" +msgstr "كوين" + +#: kcm/cubeeffectkcm.cpp:41 package/contents/ui/main.qml:40 +#, kde-format +msgid "Toggle Cube" +msgstr "مكن/عطل المكعب" + +#: package/contents/ui/PlaceholderView.qml:28 +#, kde-format +msgctxt "@info:placeholder" +msgid "" +"At least 3 virtual desktops are required to display the Cube, but only %1 is " +"present" +msgid_plural "" +"At least 3 virtual desktops are required to display the Cube, but only %1 " +"are present" +msgstr[0] "" +"يلزم وجود 3 أسطح مكتب افتراضية على الأقل لعرض المكعب، ولكن يوجد %1 فقط" +msgstr[1] "" +"يلزم وجود 3 أسطح مكتب افتراضية على الأقل لعرض المكعب، ولكن يوجد %1 فقط" +msgstr[2] "" +"يلزم وجود 3 أسطح مكتب افتراضية على الأقل لعرض المكعب، ولكن يوجد %1 فقط" +msgstr[3] "" +"يلزم وجود 3 أسطح مكتب افتراضية على الأقل لعرض المكعب، ولكن يوجد %1 فقط" +msgstr[4] "" +"يلزم وجود 3 أسطح مكتب افتراضية على الأقل لعرض المكعب، ولكن يوجد %1 فقط" +msgstr[5] "" +"يلزم وجود 3 أسطح مكتب افتراضية على الأقل لعرض المكعب، ولكن يوجد %1 فقط" + +#: package/contents/ui/PlaceholderView.qml:32 +#, kde-format +msgctxt "@action:button" +msgid "Add Virtual Desktop" +msgstr "أضف سطح المكتب افتراضي" diff --git a/po/ar/plasma_addons_engine_dict.po b/po/ar/plasma_addons_engine_dict.po new file mode 100644 index 00000000..ae8c7fb2 --- /dev/null +++ b/po/ar/plasma_addons_engine_dict.po @@ -0,0 +1,23 @@ +# Copyright (C) YEAR This file is copyright: +# This file is distributed under the same license as the kdeplasma-addons package. +# +# Zayed Al-Saidi , 2022. +msgid "" +msgstr "" +"Project-Id-Version: kdeplasma-addons\n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2024-05-27 00:39+0000\n" +"PO-Revision-Date: 2022-10-22 19:11+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: ar\n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" + +#: dictengine.cpp:77 +#, kde-format +msgid "No match found for %1" +msgstr "لم يعثر على مطابق لـ%1" diff --git a/po/ar/plasma_addons_profiles_utility.po b/po/ar/plasma_addons_profiles_utility.po new file mode 100644 index 00000000..11776f2a --- /dev/null +++ b/po/ar/plasma_addons_profiles_utility.po @@ -0,0 +1,28 @@ +# Copyright (C) YEAR This file is copyright: +# This file is distributed under the same license as the kdeplasma-addons package. +# +# Zayed Al-Saidi , 2022. +msgid "" +msgstr "" +"Project-Id-Version: kdeplasma-addons\n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2023-10-31 01:40+0000\n" +"PO-Revision-Date: 2022-10-22 19:12+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: ar\n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" + +#: profilesmodel.cpp:56 +#, kde-format +msgid "Start Kate (no arguments)" +msgstr "ابدأ «كيت» (بلا معطيات)" + +#: profilesmodel.cpp:57 +#, kde-format +msgid "New Kate Session" +msgstr "جلسة «كيت» جديدة" diff --git a/po/ar/plasma_applet_org.kde.plasma.addons.katesessions.po b/po/ar/plasma_applet_org.kde.plasma.addons.katesessions.po new file mode 100644 index 00000000..18df9b7e --- /dev/null +++ b/po/ar/plasma_applet_org.kde.plasma.addons.katesessions.po @@ -0,0 +1,56 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Safa Alfulaij , 2016. +# Zayed Al-Saidi , 2021. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2024-01-23 00:38+0000\n" +"PO-Revision-Date: 2021-07-22 23:58+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: \n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" + +#: contents/ui/KateSessionsItemDelegate.qml:92 +#, kde-format +msgid "Session name" +msgstr "اسم الجلسة" + +#: contents/ui/KateSessionsItemDelegate.qml:104 +#, kde-format +msgid "Create new session and start Kate" +msgstr "أنشئ جلسة جديدة وابدأ «كيت»" + +#: contents/ui/KateSessionsItemDelegate.qml:116 +#, kde-format +msgid "Cancel session creation" +msgstr "ألغ إنشاء الجلسة" + +#: contents/ui/main.qml:39 +#, kde-format +msgid "Kate Sessions" +msgstr "جلسات كيت" + +#: contents/ui/main.qml:48 +#, kde-format +msgid "Search…" +msgstr "ابحث..." + +#~ msgid "Delete session" +#~ msgstr "احذف الجلسة" + +#~ msgid "Start Kate (no arguments)" +#~ msgstr "ابدأ «كيت» (بلا معطيات)" + +#~ msgid "New Kate Session" +#~ msgstr "جلسة «كيت» جديدة" + +#~ msgid "New Anonymous Session" +#~ msgstr "جلسة مجهولة جديدة" diff --git a/po/ar/plasma_applet_org.kde.plasma.binaryclock.po b/po/ar/plasma_applet_org.kde.plasma.binaryclock.po new file mode 100644 index 00000000..906630ed --- /dev/null +++ b/po/ar/plasma_applet_org.kde.plasma.binaryclock.po @@ -0,0 +1,143 @@ +# translation of plasma_applet_binaryclock.po to Arabic +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# SPDX-FileCopyrightText: 2008, 2021, 2024 zayed +# Safa Alfulaij , 2015, 2017. +msgid "" +msgstr "" +"Project-Id-Version: plasma_applet_binaryclock\n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2023-12-01 01:38+0000\n" +"PO-Revision-Date: 2024-01-17 17:38+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: ar\n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" + +#: package/contents/config/config.qml:17 +#, kde-format +msgctxt "@title" +msgid "Appearance" +msgstr "المظهر" + +#: package/contents/ui/configGeneral.qml:29 +#: package/contents/ui/configGeneral.qml:59 +#, kde-format +msgctxt "@option:check" +msgid "Inactive lights" +msgstr "مصابيح خاملة" + +#: package/contents/ui/configGeneral.qml:34 +#, kde-format +msgctxt "@option:check" +msgid "Seconds" +msgstr "الثّواني" + +#: package/contents/ui/configGeneral.qml:42 +#, kde-format +msgid "Use custom color for:" +msgstr "استخدم لون مخصّص:" + +#: package/contents/ui/configGeneral.qml:46 +#, kde-format +msgctxt "@option:check" +msgid "Active lights" +msgstr "مصابيح نشطة" + +#~ msgid "Display:" +#~ msgstr "العرض:" + +#~ msgctxt "@option:check" +#~ msgid "Grid" +#~ msgstr "الشّبكة" + +#~ msgctxt "@option:check" +#~ msgid "In BCD format (decimal)" +#~ msgstr "بتنسيق BCD (عشري)" + +#, fuzzy +#~| msgid "Draw grid" +#~ msgctxt "@option:check" +#~ msgid "Draw grid" +#~ msgstr "ارسم الشّبكة" + +#, fuzzy +#~| msgid "Show inactive LEDs:" +#~ msgctxt "@option:check" +#~ msgid "Show inactive LEDs" +#~ msgstr "أظهر المصابيح الخاملة:" + +#, fuzzy +#~| msgid "Display seconds" +#~ msgctxt "@option:check" +#~ msgid "Display seconds" +#~ msgstr "اعرض الثّواني" + +#, fuzzy +#~| msgid "Colors:" +#~ msgctxt "@title:group" +#~ msgid "Colors" +#~ msgstr "الألوان:" + +#, fuzzy +#~| msgid "Use custom color for active LEDs" +#~ msgctxt "@option:check" +#~ msgid "Use custom color for active LEDs" +#~ msgstr "استخدم لون مخصّص للمصابيح النّشطة" + +#, fuzzy +#~| msgid "Use custom color for inactive LEDs" +#~ msgctxt "@option:check" +#~ msgid "Use custom color for inactive LEDs" +#~ msgstr "استخدم لون مخصّص للمصابيح الخاملة" + +#, fuzzy +#~| msgid "Appearance" +#~ msgctxt "@title:group" +#~ msgid "Appearance" +#~ msgstr "المظهر" + +#~ msgid "Check this if you want to see the inactive LEDs." +#~ msgstr "أشّر هذا إن أردت رؤية المصابيح الخاملة." + +#~ msgid "Show" +#~ msgstr "أظهر" + +#~ msgid "Use theme color" +#~ msgstr "استخدم لون السّمة" + +#~ msgid "Show the grid" +#~ msgstr "أظهر الشّبكة" + +#~ msgid "Check this if you want to see a grid around leds." +#~ msgstr "أشّر هذا إن أردت رؤية شبكة حول المصابيح." + +#~ msgid "Use custom grid color:" +#~ msgstr "استخدم لون شبكة مخصّص:" + +#~ msgid "Information" +#~ msgstr "معلومات" + +#~ msgid "Show the seconds LEDs" +#~ msgstr "أظهر مصابيح الثّواني" + +#~ msgid "" +#~ "Check this if you want to display seconds LEDs in order to see the " +#~ "seconds." +#~ msgstr "أشّر هذا إن أردت رؤية مصابيح الثّواني لترى الثّواني." + +#~ msgid "General" +#~ msgstr "عام" + +#~ msgid "Use custom on leds color:" +#~ msgstr "استعمل لون مخصص للمصباح:" + +#~ msgid "Show the off leds" +#~ msgstr "اظهر المصابيح المطفئة" + +#~ msgid "Use custom off leds color:" +#~ msgstr "استخدم لون مخصص للمصابيح المطفئة:" diff --git a/po/ar/plasma_applet_org.kde.plasma.calculator.po b/po/ar/plasma_applet_org.kde.plasma.calculator.po new file mode 100644 index 00000000..d8ce3be2 --- /dev/null +++ b/po/ar/plasma_applet_org.kde.plasma.calculator.po @@ -0,0 +1,96 @@ +# translation of plasma_applet_calculator.po to Arabic +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# zayed , 2008, 2022, 2023. +# Safa Alfulaij , 2016. +msgid "" +msgstr "" +"Project-Id-Version: plasma_applet_calculator\n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2023-10-15 01:40+0000\n" +"PO-Revision-Date: 2023-02-27 18:54+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: ar\n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" + +#: package/contents/ui/main.qml:227 +#, kde-format +msgctxt "" +"Abbreviation for result (undefined) of division by zero, max. six to nine " +"characters." +msgid "undef" +msgstr "غير معرّف" + +#: package/contents/ui/main.qml:332 +#, kde-format +msgctxt "@label calculation result" +msgid "Result" +msgstr "النتيجة" + +#: package/contents/ui/main.qml:361 +#, kde-format +msgctxt "Text of the clear button" +msgid "C" +msgstr "امحِ" + +#: package/contents/ui/main.qml:374 +#, kde-format +msgctxt "Text of the division button" +msgid "÷" +msgstr "÷" + +#: package/contents/ui/main.qml:387 +#, kde-format +msgctxt "Text of the multiplication button" +msgid "×" +msgstr "×" + +#: package/contents/ui/main.qml:399 +#, kde-format +msgctxt "Text of the all clear button" +msgid "AC" +msgstr "امحِ الكلّ" + +#: package/contents/ui/main.qml:451 +#, kde-format +msgctxt "Text of the minus button" +msgid "-" +msgstr "-" + +#: package/contents/ui/main.qml:503 +#, kde-format +msgctxt "Text of the plus button" +msgid "+" +msgstr "+" + +#: package/contents/ui/main.qml:554 +#, kde-format +msgctxt "Text of the equals button" +msgid "=" +msgstr "=" + +#~ msgctxt "The − button of the calculator" +#~ msgid "−" +#~ msgstr "−" + +#~ msgid "ERROR" +#~ msgstr "خطأ" + +#~ msgid "ERROR: DIV BY 0" +#~ msgstr "خطأ: قسمت على 0" + +#~ msgid "Copy" +#~ msgstr "انسخ" + +#~ msgid "Paste" +#~ msgstr "ألصق" + +# أعتقد أن استخدام \ يناسب العرب أكثر لاستخدام الرمز المذكور في الكتب المدرسية حسب ما أذكر +#~ msgctxt "The ∕ button of the calculator" +#~ msgid "∕" +#~ msgstr "÷" diff --git a/po/ar/plasma_applet_org.kde.plasma.colorpicker.po b/po/ar/plasma_applet_org.kde.plasma.colorpicker.po new file mode 100644 index 00000000..b4988ee3 --- /dev/null +++ b/po/ar/plasma_applet_org.kde.plasma.colorpicker.po @@ -0,0 +1,152 @@ +# translation of plasma_applet_kolourpicker.po to Arabic +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# zayed , 2008, 2021, 2022, 2023. +# Safa Alfulaij , 2017. +msgid "" +msgstr "" +"Project-Id-Version: plasma_applet_kolourpicker\n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2024-06-07 00:40+0000\n" +"PO-Revision-Date: 2023-05-15 18:09+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: ar\n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" + +#: package/contents/config/config.qml:11 +#, kde-format +msgctxt "@title" +msgid "General" +msgstr "عامّ" + +#: package/contents/ui/ColorCircle.qml:41 +#, kde-format +msgctxt "@info:usagetip" +msgid "Middle-click to copy the color code" +msgstr "انقر بالزر الأوسط لنسخ رمز اللون" + +#: package/contents/ui/CompactRepresentation.qml:77 +#, kde-format +msgctxt "@info:tooltip" +msgid "Pick color" +msgstr "انتقِ لونًا" + +#: package/contents/ui/CompactRepresentation.qml:84 +#, kde-kuit-format +msgctxt "@info:usagetip" +msgid "" +"Drag a color code here to save itDrag an image file here to get its " +"average color" +msgstr "اسحب رمز اللون لتحفظه اسحب ملف الصورة هنا لتحصل على اللون المتوسط" + +#: package/contents/ui/configGeneral.qml:23 +#, kde-format +msgctxt "@label:listbox" +msgid "Default color format:" +msgstr "تنسيق اللون المبدئيّ:" + +#: package/contents/ui/configGeneral.qml:33 +#, kde-format +msgctxt "@option:check" +msgid "Automatically copy color to clipboard" +msgstr "انسخ اللون إلى الحافظة آليًّا" + +#: package/contents/ui/configGeneral.qml:41 +#, kde-format +msgctxt "@label" +msgid "When pressing the keyboard shortcut:" +msgstr "عند ضغط اختصار لوحة المفاتيح:" + +#: package/contents/ui/configGeneral.qml:42 +#, kde-format +msgctxt "@option:radio" +msgid "Pick a color" +msgstr "انتقِ لونًا" + +#: package/contents/ui/configGeneral.qml:48 +#, kde-format +msgctxt "@option:radio" +msgid "Show history" +msgstr "أظهر التأريخ" + +#: package/contents/ui/configGeneral.qml:58 +#, kde-format +msgctxt "@label" +msgid "Preview count:" +msgstr "عاين العدد:" + +#: package/contents/ui/main.qml:121 +#, kde-format +msgctxt "@action" +msgid "Open Color Dialog" +msgstr "افتح حواريّ الألوان" + +#: package/contents/ui/main.qml:126 +#, kde-format +msgctxt "@action" +msgid "Clear History" +msgstr "امسح التاريخ" + +#: package/contents/ui/main.qml:134 +#, kde-format +msgctxt "@action" +msgid "View History" +msgstr "عرض التاريخ" + +#: package/contents/ui/main.qml:142 +#, kde-format +msgctxt "@title:menu" +msgid "Copy to Clipboard" +msgstr "انسخ إلى الحافظة" + +#: package/contents/ui/main.qml:182 +#, kde-format +msgctxt "@info:usagetip" +msgid "No colors" +msgstr "بدون ألوان" + +#: package/contents/ui/main.qml:182 +#, kde-format +msgctxt "@info:status when color picking is unavailable" +msgid "Color picking unavailable when compositing is disabled" +msgstr "انتقاء لون غير ممكن عندما يكون التركيب معطل" + +#: package/contents/ui/main.qml:183 +#, kde-format +msgctxt "@info:status when color pick is unavailable" +msgid "" +"Compositing has been manually disabled or blocked by a running application" +msgstr "عُطل التركيب يدويا أو حظر بأحد التطبيقات التي تعمل" + +#: package/contents/ui/main.qml:190 +#, kde-format +msgctxt "@action:button" +msgid "Pick Color" +msgstr "انتقِ لونًا" + +#: package/contents/ui/main.qml:198 +#, kde-format +msgctxt "@action:button open kwincompositing KCM" +msgid "Configure Compositing" +msgstr "اضبط التركيب" + +#: package/contents/ui/main.qml:384 +#, kde-format +msgctxt "@info:progress just copied a color to clipboard" +msgid "Copied!" +msgstr "نُسخ!" + +#: package/contents/ui/main.qml:395 +#, kde-format +msgctxt "@action:button" +msgid "Delete" +msgstr "احذف" + +#~ msgctxt "@info:tooltip" +#~ msgid "Color options" +#~ msgstr "خيارات اللون" diff --git a/po/ar/plasma_applet_org.kde.plasma.comic.po b/po/ar/plasma_applet_org.kde.plasma.comic.po new file mode 100644 index 00000000..2ea92543 --- /dev/null +++ b/po/ar/plasma_applet_org.kde.plasma.comic.po @@ -0,0 +1,411 @@ +# translation of plasma_applet_comic.po to Arabic +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# zayed , 2008, 2021, 2022. +msgid "" +msgstr "" +"Project-Id-Version: plasma_applet_comic\n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2024-01-23 00:38+0000\n" +"PO-Revision-Date: 2022-08-08 23:15+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: Arabic \n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" + +#: comic.cpp:80 +#, kde-format +msgctxt "@action comic strip" +msgid "&Next Tab with a New Strip" +msgstr "الل&سان التالي بشريط جديد" + +#: comic.cpp:86 +#, kde-format +msgctxt "@action" +msgid "Jump to &First Strip" +msgstr "ا&قفز إلى الشريط الأول" + +#: comic.cpp:90 +#, kde-format +msgctxt "@action" +msgid "Jump to &Current Strip" +msgstr "اقفز إلى الشريط ال&حالي" + +#: comic.cpp:94 +#, kde-format +msgctxt "@action" +msgid "Jump to Strip…" +msgstr "اقفز إلى الشريط..." + +#: comic.cpp:98 +#, kde-format +msgctxt "@action" +msgid "Visit the Website" +msgstr "زر موقع الانترنت" + +#: comic.cpp:102 +#, kde-format +msgctxt "@action:inmenu %1 is the name of a web browser" +msgid "View in %1" +msgstr "اعرض في %1" + +#: comic.cpp:110 +#, kde-format +msgctxt "@action" +msgid "Visit the Shop &Website" +msgstr "زر موقع ال&محل" + +#: comic.cpp:115 +#, kde-format +msgctxt "@action" +msgid "&Save Comic As…" +msgstr "ا&حفظ الرسمة كـ ..." + +#: comic.cpp:120 +#, kde-format +msgctxt "@option:check Context menu of comic image" +msgid "&Actual Size" +msgstr "الحجم ال&فعلي" + +#: comic.cpp:128 +#, kde-format +msgctxt "@option:check Context menu of comic image" +msgid "Store Current &Position" +msgstr "احفظ الم&وضع الحالي" + +#: comicdata.cpp:86 +#, kde-format +msgctxt "an abbreviation for Number" +msgid "# %1" +msgstr "# %1" + +#: package/contents/config/config.qml:13 +#, kde-format +msgctxt "@title" +msgid "General" +msgstr "عامّ" + +#: package/contents/config/config.qml:18 +#, kde-format +msgctxt "@title" +msgid "Appearance" +msgstr "المظهر" + +#: package/contents/config/config.qml:23 +#, kde-format +msgctxt "@title" +msgid "Advanced" +msgstr "متقدّم" + +#: package/contents/ui/ComicBottomInfo.qml:63 +#, kde-format +msgctxt "@info:tooltip" +msgid "Jump to strip…" +msgstr "اقفز إلى الشريط..." + +#: package/contents/ui/ComicBottomInfo.qml:104 +#, kde-format +msgctxt "@info:tooltip" +msgid "Visit the comic website" +msgstr "زر موقع الرسمة" + +#: package/contents/ui/configAdvanced.qml:35 +#, kde-format +msgctxt "@label:spinbox" +msgid "Comic cache:" +msgstr "خبيئة الرسمات:" + +#: package/contents/ui/configAdvanced.qml:44 +#, kde-format +msgctxt "@item:valuesuffix spacing to number + unit" +msgid "strip per comic" +msgid_plural "strips per comic" +msgstr[0] "شريط لكل رسوم" +msgstr[1] "شريط لكل رسوم" +msgstr[2] "شريطين لكل رسوم" +msgstr[3] "أشرطة لكل رسوم" +msgstr[4] "شريطاً لكل رسوم" +msgstr[5] "شريط لكل رسوم" + +#: package/contents/ui/configAdvanced.qml:51 +#, kde-format +msgctxt "@option:check" +msgid "Display error when downloading comic fails" +msgstr "اعرض الخطأ عند فشل تحميل الرسوم" + +#: package/contents/ui/configAppearance.qml:42 +#, kde-format +msgctxt "Heading for showing various elements of a comic" +msgid "Show:" +msgstr "أظهر:" + +#: package/contents/ui/configAppearance.qml:43 +#, kde-format +msgctxt "@option:check" +msgid "Comic title" +msgstr "عنوان الرسمة" + +#: package/contents/ui/configAppearance.qml:49 +#, kde-format +msgctxt "@option:check" +msgid "Comic identifier" +msgstr "معرف الرسمة" + +#: package/contents/ui/configAppearance.qml:55 +#, kde-format +msgctxt "@option:check" +msgid "Comic author" +msgstr "مؤلف الرسمة" + +#: package/contents/ui/configAppearance.qml:61 +#, kde-format +msgctxt "@option:check" +msgid "Comic URL" +msgstr "رابط الرسمة" + +#: package/contents/ui/configAppearance.qml:71 +#, kde-format +msgid "Show navigation buttons:" +msgstr "أظهر أزرار التنقل:" + +#: package/contents/ui/configAppearance.qml:72 +#, kde-format +msgctxt "@option:check" +msgid "Always" +msgstr "دائما" + +#: package/contents/ui/configAppearance.qml:79 +#, kde-format +msgctxt "@option:check" +msgid "Only on hover" +msgstr "فقط عند التحويم" + +#: package/contents/ui/configGeneral.qml:52 +#, kde-format +msgctxt "@title:group" +msgid "Comics:" +msgstr "الرسمات:" + +#: package/contents/ui/configGeneral.qml:72 +#, kde-format +msgctxt "@action:button" +msgid "Get New Comics…" +msgstr "احصل على رسمات جديدة..." + +#: package/contents/ui/configGeneral.qml:83 +#, kde-format +msgctxt "@option:check" +msgid "Middle-click on comic to display at original size" +msgstr "النقر بالزر الأوسط لعرض حجمها الفعلي" + +#: package/contents/ui/configGeneral.qml:92 +#, kde-format +msgctxt "@label:spinbox" +msgid "Check for new plugins every:" +msgstr "التمس الملحقات الجديدة كل:" + +#: package/contents/ui/configGeneral.qml:101 +#, kde-format +msgctxt "@item:valuesuffix spacing to number + unit" +msgid "day" +msgid_plural "days" +msgstr[0] "يوم" +msgstr[1] "يوم" +msgstr[2] "يومين" +msgstr[3] "أيّام" +msgstr[4] "يومًا" +msgstr[5] "يوم" + +#: package/contents/ui/configGeneral.qml:107 +#, kde-format +msgctxt "@label:spinbox" +msgid "Check for new comics every:" +msgstr "التمس الرسمات الجديدة كل:" + +#: package/contents/ui/configGeneral.qml:116 +#, kde-format +msgctxt "@item:valuesuffix spacing to number + unit (minutes)" +msgid "minute" +msgid_plural "minutes" +msgstr[0] "دقيقة" +msgstr[1] "دقيقة" +msgstr[2] "دقيقتين" +msgstr[3] "دقائق" +msgstr[4] "دقيقة" +msgstr[5] "دقيقة" + +#: stripselector.cpp:31 stripselector.cpp:101 +#, kde-format +msgctxt "@title:window" +msgid "Go to Strip" +msgstr "اذهب للشريط" + +#: stripselector.cpp:39 +#, kde-format +msgctxt "@label:spinbox" +msgid "&Strip number:" +msgstr "رقم ال&شريط:" + +#: stripselector.cpp:102 +#, kde-format +msgctxt "@label:textbox" +msgid "Strip identifier:" +msgstr "معرف الشريط:" + +#~ msgctxt "@action" +#~ msgid "&Create Comic Book Archive…" +#~ msgstr "أ&نشئ أرشيف كتاب القصص المصورة ..." + +#~ msgid "Archiving comic failed" +#~ msgstr "فشلت الأرشفة" + +#~ msgctxt "@title:window" +#~ msgid "Create %1 Comic Book Archive" +#~ msgstr "أ&نشئ أرشيف كتاب القصص المصورة %1..." + +#~ msgid "Destination:" +#~ msgstr "المقصد:" + +#~ msgid "*.cbz|Comic Book Archive (Zip)" +#~ msgstr "*.cbz|أرشيف القصص المصورة (Zip)" + +#~ msgid "The range of comic strips to archive." +#~ msgstr "مدى أشرطة الرسومات لأرشفتها" + +#~ msgid "Range:" +#~ msgstr "المدى:" + +#~ msgid "All" +#~ msgstr "الكلّ" + +#~ msgid "From beginning to …" +#~ msgstr "من البداية إلى …" + +#~ msgid "From end to …" +#~ msgstr "من النهاية إلى …" + +#~ msgid "Manual range" +#~ msgstr "مدى يدوي" + +#~ msgctxt "in a range: from to" +#~ msgid "From:" +#~ msgstr "من:" + +#~ msgctxt "in a range: from to" +#~ msgid "To:" +#~ msgstr "إلى:" + +#~ msgid "dd.MM.yyyy" +#~ msgstr "dd.MM.yyyy" + +#~ msgid "No zip file is existing, aborting." +#~ msgstr "لا يوجد ملف zip، أحبطت العملية" + +#~ msgid "An error happened for identifier %1." +#~ msgstr "حصلت مشكلة للمعرف %1." + +#~ msgid "Failed creating the file with identifier %1." +#~ msgstr "فشل في إنشاء الملف بالمعرف %1." + +#~ msgid "Creating Comic Book Archive" +#~ msgstr "ينشئ أرشف كتاب القصص المصورة" + +#~ msgid "Failed adding a file to the archive." +#~ msgstr "فشل في إضافة ملف إلى الأرشيف" + +#~ msgid "Could not create the archive at the specified location." +#~ msgstr "لا يمكن إنشاء الأرشيف في الموضع المحدد." + +#~ msgid "Getting comic strip failed:" +#~ msgstr "فشل في الحصول إلى شريط الرسمة:" + +#~ msgid "" +#~ "Maybe there is no Internet connection.\n" +#~ "Maybe the comic plugin is broken.\n" +#~ "Another reason might be that there is no comic for this day/number/" +#~ "string, so choosing a different one might work." +#~ msgstr "" +#~ "ربما لا يوجد اتصال بالإنترنت. \n" +#~ "ربما عطل ملحق الرسوم المصورة. \n" +#~ "قد يكون السبب الآخر هو عدم وجود رسوم هزلية لهذا اليوم / الرقم / السلسلة ، " +#~ "لذا قد ينجح اختيار واحد مختلف." + +#~ msgid "" +#~ "\n" +#~ "\n" +#~ "Choose the previous strip to go to the last cached strip." +#~ msgstr "" +#~ "\n" +#~ "\n" +#~ "اختر الشريط السابق للانتقال إلى آخر شريط مخزن مؤقتًا." + +#, fuzzy +#~| msgid "Download new comics" +#~ msgctxt "@title:window" +#~ msgid "Download Comics" +#~ msgstr "نزل رسمات هزلية جديدة" + +#, fuzzy +#~| msgid "Show arrows only on &hover:" +#~ msgctxt "@option:check" +#~ msgid "Show arrows only on mouse-over" +#~ msgstr "أظهر الأسهم عند الم&رور عليها فقط:" + +#, fuzzy +#~| msgid "Information" +#~ msgctxt "@title:group" +#~ msgid "Information" +#~ msgstr "المعلومات" + +#, fuzzy +#~| msgid "Show comic &URL:" +#~ msgctxt "@option:check" +#~ msgid "Show comic URL" +#~ msgstr "أظهر و&صلة الرسمة:" + +#, fuzzy +#~ msgctxt "@label:spinbox" +#~ msgid "Automatically update comic plugins:" +#~ msgstr "&بدل بين الألسنة آليا:" + +#, fuzzy +#~| msgid "Appearance" +#~ msgctxt "@title:group" +#~ msgid "Appearance" +#~ msgstr "المظهر" + +#~ msgid "Maximum &Size of Widget" +#~ msgstr "أقصى &حجم الودجة" + +#, fuzzy +#~| msgid "&Hide Tabbar:" +#~ msgid "Tabbar" +#~ msgstr "أخف &شريط الألسنة:" + +#~ msgid "Press the \"Get New Comics ...\" button to install comics." +#~ msgstr "اضعط على زر \"احصل على رسوم هزلية جديدة...\" لتثبت الرسوم الهزلية" + +#~ msgid "Tabs" +#~ msgstr "الألسنة" + +#~ msgid "Use &tabs:" +#~ msgstr "اس&تخدم الألسنة:" + +#~ msgid "Pressing Ctrl and scrolling also changes the tabs." +#~ msgstr "الضغط على زر Ctrl و التمرير يغر الألسنة أيضا." + +#~ msgid "&Hide Tabbar:" +#~ msgstr "أخف &شريط الألسنة:" + +#~ msgid "Interacting with the comic widget restarts the timer." +#~ msgstr "التفاعل مع ودجة الرسمات الهزلية يعيد ضبط المؤقت." + +#~ msgid "hh 'Hours' mm 'Mins' ss 'Secs'" +#~ msgstr "hh 'ساعات' mm 'دقائق' ss 'ثواني'" + +#~ msgid "Automatically &switch tabs:" +#~ msgstr "&بدل بين الألسنة آليا:" diff --git a/po/ar/plasma_applet_org.kde.plasma.diskquota.po b/po/ar/plasma_applet_org.kde.plasma.diskquota.po new file mode 100644 index 00000000..876d84e8 --- /dev/null +++ b/po/ar/plasma_applet_org.kde.plasma.diskquota.po @@ -0,0 +1,81 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Safa Alfulaij , 2016, 2017. +# Zayed Al-Saidi , 2022. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2024-09-02 00:40+0000\n" +"PO-Revision-Date: 2022-08-08 23:16+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: Arabic \n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" + +#: package/contents/ui/main.qml:77 +#, kde-format +msgctxt "@info:status" +msgid "No quota restrictions found" +msgstr "لم يُعثر على تحديدات للحصّة." + +#: package/contents/ui/main.qml:77 +#, kde-format +msgctxt "@info:status" +msgid "Quota tool not found" +msgstr "لم يُعثر على أداة Quota." + +#: package/contents/ui/main.qml:78 +#, kde-format +msgctxt "@info:usagetip" +msgid "Please install 'quota'" +msgstr "فضلًا ثبّت 'quota'" + +#: plugin/DiskQuota.cpp:44 plugin/DiskQuota.cpp:166 plugin/DiskQuota.cpp:252 +#, kde-format +msgid "Disk Quota" +msgstr "حصّة القرص" + +#: plugin/DiskQuota.cpp:45 +#, kde-format +msgid "Please install 'quota'" +msgstr "فضلًا ثبّت 'quota'" + +#: plugin/DiskQuota.cpp:167 +#, kde-format +msgid "Running quota failed" +msgstr "فشل تشغيل quota" + +#: plugin/DiskQuota.cpp:227 +#, kde-format +msgctxt "usage of quota, e.g.: '/home/bla: 38% used'" +msgid "%1: %2% used" +msgstr "‏‎%1‎: ‏%2% مستخدم" + +#: plugin/DiskQuota.cpp:228 +#, kde-format +msgctxt "e.g.: 12 GiB of 20 GiB" +msgid "%1 of %2" +msgstr "%1 من %2" + +#: plugin/DiskQuota.cpp:229 +#, kde-format +msgctxt "e.g.: 8 GiB free" +msgid "%1 free" +msgstr "%1 حرّة" + +#: plugin/DiskQuota.cpp:249 +#, kde-format +msgctxt "example: Quota: 83% used" +msgid "Quota: %1% used" +msgstr "الحصّة: %1% مستخدمة" + +#: plugin/DiskQuota.cpp:253 +#, kde-format +msgid "No quota restrictions found." +msgstr "لم يُعثر على تحديدات للحصّة." diff --git a/po/ar/plasma_applet_org.kde.plasma.fifteenpuzzle.po b/po/ar/plasma_applet_org.kde.plasma.fifteenpuzzle.po new file mode 100644 index 00000000..daf35d6a --- /dev/null +++ b/po/ar/plasma_applet_org.kde.plasma.fifteenpuzzle.po @@ -0,0 +1,170 @@ +# translation of plasma_applet_fifteenPuzzle.po to Arabic +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# zayed , 2008, 2021. +# Safa Alfulaij , 2016, 2017. +msgid "" +msgstr "" +"Project-Id-Version: plasma_applet_fifteenPuzzle\n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2024-01-23 00:38+0000\n" +"PO-Revision-Date: 2021-12-22 18:24+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: ar\n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" + +#: package/contents/config/config.qml:13 +#, kde-format +msgctxt "@title" +msgid "Appearance" +msgstr "المظهر" + +#: package/contents/ui/configAppearance.qml:43 +#, kde-format +msgctxt "@label:spinbox" +msgid "Grid size:" +msgstr "حجم الشبكة:" + +#: package/contents/ui/configAppearance.qml:52 +#, kde-format +msgid "Background:" +msgstr "الخلفيّة:" + +#: package/contents/ui/configAppearance.qml:58 +#, kde-format +msgid "Color:" +msgstr "اللون:" + +#: package/contents/ui/configAppearance.qml:73 +#, kde-format +msgid "Image:" +msgstr "صورة:" + +#: package/contents/ui/configAppearance.qml:81 +#, kde-format +msgctxt "@info:placeholder" +msgid "Path to custom image…" +msgstr "مسار الصّورة المخصّصة..." + +#: package/contents/ui/configAppearance.qml:100 +#, kde-format +msgctxt "@info:tooltip" +msgid "Choose image…" +msgstr "اختر صورة..." + +#: package/contents/ui/configAppearance.qml:108 +#, kde-format +msgctxt "@title:window" +msgid "Choose an Image" +msgstr "اختر صورة..." + +#: package/contents/ui/configAppearance.qml:113 +#, kde-format +msgid "Image Files (*.png *.jpg *.jpeg *.bmp *.svg *.svgz)" +msgstr "ملفات الصّور (*.png *.jpg *.jpeg *.bmp *.svg *.svgz)" + +#: package/contents/ui/configAppearance.qml:127 +#, kde-format +msgid "Tiles:" +msgstr "القراميد:" + +#: package/contents/ui/configAppearance.qml:131 +#, kde-format +msgid "Colored numbers:" +msgstr "الأرقام الملونة:" + +#: package/contents/ui/FifteenPuzzle.qml:259 +#, kde-format +msgctxt "The time since the puzzle started, in minutes and seconds" +msgid "Time: %1" +msgstr "الوقت: %1" + +#: package/contents/ui/FifteenPuzzle.qml:302 +#, kde-format +msgctxt "@action:button" +msgid "Shuffle" +msgstr "اخلط" + +#: package/contents/ui/FifteenPuzzle.qml:340 +#, kde-format +msgctxt "@info" +msgid "Solved! Try again." +msgstr "حُلّت! جرّب أخرى." + +#: package/contents/ui/main.qml:26 +#, kde-format +msgid "Fifteen Puzzle" +msgstr "أحجية الخمسة عشر" + +#: package/contents/ui/main.qml:27 +#, kde-format +msgid "Solve by arranging in order" +msgstr "حلّها بترتيب القطع ترتيبًا صحيحًا" + +#, fuzzy +#~| msgid "Size" +#~ msgctxt "@label:spinbox" +#~ msgid "Size:" +#~ msgstr "الحجم" + +#, fuzzy +#~| msgid "Piece color" +#~ msgctxt "@label:chooser" +#~ msgid "Piece color:" +#~ msgstr "لون القطعة" + +#, fuzzy +#~| msgid "Number color" +#~ msgctxt "@label:chooser" +#~ msgid "Number color:" +#~ msgstr "لون العدد" + +#, fuzzy +#~| msgid "Use custom image" +#~ msgctxt "@option:check" +#~ msgid "Use custom image" +#~ msgstr "استخدم صورة مخصّصة" + +#, fuzzy +#~| msgid "Browse..." +#~ msgctxt "@info:tooltip" +#~ msgid "Browse..." +#~ msgstr "تصفّح..." + +#, fuzzy +#~| msgid "Show numerals" +#~ msgctxt "@option:check" +#~ msgid "Show numerals" +#~ msgstr "أظهر الأعداد" + +#, fuzzy +#~| msgid "Appearance" +#~ msgctxt "@title:group" +#~ msgid "Appearance" +#~ msgstr "المظهر" + +#~ msgid "General" +#~ msgstr "عام" + +#~ msgid "Shuffle Pieces" +#~ msgstr "حرك القطع" + +#, fuzzy +#~ msgid "Use plain pieces:" +#~ msgstr "استعمل قطع بسيطة" + +#~ msgctxt "@title:window" +#~ msgid "Configure Fifteen Puzzle" +#~ msgstr "اضبط أحجية الخمسة عشر" + +#~ msgctxt "@body:window" +#~ msgid "You have to provide a valid image" +#~ msgstr "يجب أن تعطي صورة صالحة" + +#~ msgid "Actions" +#~ msgstr "الإجراءات" diff --git a/po/ar/plasma_applet_org.kde.plasma.fuzzyclock.po b/po/ar/plasma_applet_org.kde.plasma.fuzzyclock.po new file mode 100644 index 00000000..22c710dc --- /dev/null +++ b/po/ar/plasma_applet_org.kde.plasma.fuzzyclock.po @@ -0,0 +1,887 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Safa Alfulaij , 2014, 2017. +# Zayed Al-Saidi , 2021. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2024-01-23 00:38+0000\n" +"PO-Revision-Date: 2021-07-04 22:06+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: ar\n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" + +#: package/contents/config/config.qml:13 +#, kde-format +msgctxt "@title" +msgid "Appearance" +msgstr "المظهر" + +#: package/contents/ui/configAppearance.qml:24 +#, kde-format +msgctxt "@title:group" +msgid "Font:" +msgstr "الخط:" + +#: package/contents/ui/configAppearance.qml:25 +#, kde-format +msgctxt "@option:check" +msgid "Bold text" +msgstr "نصّ عريض" + +#: package/contents/ui/configAppearance.qml:30 +#, kde-format +msgctxt "@option:check" +msgid "Italic text" +msgstr "نصّ مائل" + +#: package/contents/ui/configAppearance.qml:39 +#, kde-format +msgctxt "@title:group" +msgid "Fuzzyness:" +msgstr "الغموض:" + +#: package/contents/ui/configAppearance.qml:49 +#, kde-format +msgctxt "@item:inrange" +msgid "Accurate" +msgstr "دقيق" + +#: package/contents/ui/configAppearance.qml:58 +#, kde-format +msgctxt "@item:inrange" +msgid "Fuzzy" +msgstr "مجنون" + +#: package/contents/ui/FuzzyClock.qml:35 +#, kde-format +msgid "One o’clock" +msgstr "الساعة الواحدة" + +#: package/contents/ui/FuzzyClock.qml:36 +#, kde-format +msgid "Five past one" +msgstr "خمسُ دقائق بعد الواحدة" + +#: package/contents/ui/FuzzyClock.qml:37 +#, kde-format +msgid "Ten past one" +msgstr "عشرُ دقائق بعد الواحدة" + +#: package/contents/ui/FuzzyClock.qml:38 +#, kde-format +msgid "Quarter past one" +msgstr "ربعُ ساعة بعد الواحدة" + +#: package/contents/ui/FuzzyClock.qml:39 +#, kde-format +msgid "Twenty past one" +msgstr "عشرون دقيقة بعد الواحدة" + +#: package/contents/ui/FuzzyClock.qml:40 +#, kde-format +msgid "Twenty-five past one" +msgstr "خمسٌ وعشرون دقيقة بعد الواحدة" + +#: package/contents/ui/FuzzyClock.qml:41 +#, kde-format +msgid "Half past one" +msgstr "نصفُ ساعة بعد الواحدة" + +#: package/contents/ui/FuzzyClock.qml:42 +#, kde-format +msgid "Twenty-five to two" +msgstr "خمسٌ وعشرون دقيقة حتّى الثانية" + +#: package/contents/ui/FuzzyClock.qml:43 +#, kde-format +msgid "Twenty to two" +msgstr "عشرون دقيقة حتّى الثانية" + +#: package/contents/ui/FuzzyClock.qml:44 +#, kde-format +msgid "Quarter to two" +msgstr "ربعُ ساعة حتّى الثانية" + +#: package/contents/ui/FuzzyClock.qml:45 +#, kde-format +msgid "Ten to two" +msgstr "عشرُ دقائق حتّى الثانية" + +#: package/contents/ui/FuzzyClock.qml:46 +#, kde-format +msgid "Five to two" +msgstr "عشرون دقيقة حتّى الثانية" + +#: package/contents/ui/FuzzyClock.qml:47 +#, kde-format +msgid "Two o’clock" +msgstr "الساعة الثانية" + +#: package/contents/ui/FuzzyClock.qml:48 +#, kde-format +msgid "Five past two" +msgstr "خمسُ دقائق بعد الثانية" + +#: package/contents/ui/FuzzyClock.qml:49 +#, kde-format +msgid "Ten past two" +msgstr "عشرُ دقائق بعد الثانية" + +#: package/contents/ui/FuzzyClock.qml:50 +#, kde-format +msgid "Quarter past two" +msgstr "ربعُ ساعة بعد الثانية" + +#: package/contents/ui/FuzzyClock.qml:51 +#, kde-format +msgid "Twenty past two" +msgstr "عشرون دقيقة بعد الثانية" + +#: package/contents/ui/FuzzyClock.qml:52 +#, kde-format +msgid "Twenty-five past two" +msgstr "خمسٌ وعشرون دقيقة بعد الثانية" + +#: package/contents/ui/FuzzyClock.qml:53 +#, kde-format +msgid "Half past two" +msgstr "نصفُ ساعة بعد الثانية" + +#: package/contents/ui/FuzzyClock.qml:54 +#, kde-format +msgid "Twenty-five to three" +msgstr "خمسٌ وعشرون دقيقة حتّى الثالثة" + +#: package/contents/ui/FuzzyClock.qml:55 +#, kde-format +msgid "Twenty to three" +msgstr "عشرون دقيقة حتّى الثالثة" + +#: package/contents/ui/FuzzyClock.qml:56 +#, kde-format +msgid "Quarter to three" +msgstr "ربعُ ساعة حتّى الثالثة" + +#: package/contents/ui/FuzzyClock.qml:57 +#, kde-format +msgid "Ten to three" +msgstr "عشرُ دقائق حتّى الثالثة" + +#: package/contents/ui/FuzzyClock.qml:58 +#, kde-format +msgid "Five to three" +msgstr "خمسُ دقائق حتّى الثالثة" + +#: package/contents/ui/FuzzyClock.qml:59 +#, kde-format +msgid "Three o’clock" +msgstr "الساعة الثالثة" + +#: package/contents/ui/FuzzyClock.qml:60 +#, kde-format +msgid "Five past three" +msgstr "خمسُ دقائق بعد الثالثة" + +#: package/contents/ui/FuzzyClock.qml:61 +#, kde-format +msgid "Ten past three" +msgstr "عشرُ دقائق بعد الثالثة" + +#: package/contents/ui/FuzzyClock.qml:62 +#, kde-format +msgid "Quarter past three" +msgstr "ربعُ ساعة بعد الثالثة" + +#: package/contents/ui/FuzzyClock.qml:63 +#, kde-format +msgid "Twenty past three" +msgstr "عشرون دقيقة بعد الثالثة" + +#: package/contents/ui/FuzzyClock.qml:64 +#, kde-format +msgid "Twenty-five past three" +msgstr "خمسٌ وعشرون دقيقة بعد الثالثة" + +#: package/contents/ui/FuzzyClock.qml:65 +#, kde-format +msgid "Half past three" +msgstr "نصفُ ساعة بعد الثالثة" + +#: package/contents/ui/FuzzyClock.qml:66 +#, kde-format +msgid "Twenty-five to four" +msgstr "خمسٌ وعشرون دقيقة حتّى الرابعة" + +#: package/contents/ui/FuzzyClock.qml:67 +#, kde-format +msgid "Twenty to four" +msgstr "عشرون دقيقة حتّى الرابعة" + +#: package/contents/ui/FuzzyClock.qml:68 +#, kde-format +msgid "Quarter to four" +msgstr "ربعُ ساعة حتّى الرابعة" + +#: package/contents/ui/FuzzyClock.qml:69 +#, kde-format +msgid "Ten to four" +msgstr "عشرُ دقائق حتّى الرابعة" + +#: package/contents/ui/FuzzyClock.qml:70 +#, kde-format +msgid "Five to four" +msgstr "خمسُ دقائق حتّى الرابعة" + +#: package/contents/ui/FuzzyClock.qml:71 +#, kde-format +msgid "Four o’clock" +msgstr "الساعة الرابعة" + +#: package/contents/ui/FuzzyClock.qml:72 +#, kde-format +msgid "Five past four" +msgstr "خمسُ دقائق بعد الرابعة" + +#: package/contents/ui/FuzzyClock.qml:73 +#, kde-format +msgid "Ten past four" +msgstr "عشرُ دقائق بعد الرابعة" + +#: package/contents/ui/FuzzyClock.qml:74 +#, kde-format +msgid "Quarter past four" +msgstr "ربعُ ساعة بعد الرابعة" + +#: package/contents/ui/FuzzyClock.qml:75 +#, kde-format +msgid "Twenty past four" +msgstr "عشرون دقيقة بعد الرابعة" + +#: package/contents/ui/FuzzyClock.qml:76 +#, kde-format +msgid "Twenty-five past four" +msgstr "خمسٌ وعشرون دقيقة بعد الرابعة" + +#: package/contents/ui/FuzzyClock.qml:77 +#, kde-format +msgid "Half past four" +msgstr "نصفُ ساعة بعد الرابعة" + +#: package/contents/ui/FuzzyClock.qml:78 +#, kde-format +msgid "Twenty-five to five" +msgstr "خمسٌ وعشرون دقيقة حتّى الخامسة" + +#: package/contents/ui/FuzzyClock.qml:79 +#, kde-format +msgid "Twenty to five" +msgstr "عشرون دقيقة حتّى الخامسة" + +#: package/contents/ui/FuzzyClock.qml:80 +#, kde-format +msgid "Quarter to five" +msgstr "ربعُ ساعة حتّى الخامسة" + +#: package/contents/ui/FuzzyClock.qml:81 +#, kde-format +msgid "Ten to five" +msgstr "عشرُ دقائق حتّى الخامسة" + +#: package/contents/ui/FuzzyClock.qml:82 +#, kde-format +msgid "Five to five" +msgstr "خمسُ دقائق حتّى الخامسة" + +#: package/contents/ui/FuzzyClock.qml:83 +#, kde-format +msgid "Five o’clock" +msgstr "الساعة الخامسة" + +#: package/contents/ui/FuzzyClock.qml:84 +#, kde-format +msgid "Five past five" +msgstr "خمسُ دقائق بعد الخامسة" + +#: package/contents/ui/FuzzyClock.qml:85 +#, kde-format +msgid "Ten past five" +msgstr "عشرُ دقائق بعد الخامسة" + +#: package/contents/ui/FuzzyClock.qml:86 +#, kde-format +msgid "Quarter past five" +msgstr "ربعُ ساعة بعد الخامسة" + +#: package/contents/ui/FuzzyClock.qml:87 +#, kde-format +msgid "Twenty past five" +msgstr "عشرون دقيقة بعد الخامسة" + +#: package/contents/ui/FuzzyClock.qml:88 +#, kde-format +msgid "Twenty-five past five" +msgstr "خمسٌ وعشرون دقيقة بعد الخامسة" + +#: package/contents/ui/FuzzyClock.qml:89 +#, kde-format +msgid "Half past five" +msgstr "نصفُ ساعة بعد الخامسة" + +#: package/contents/ui/FuzzyClock.qml:90 +#, kde-format +msgid "Twenty-five to six" +msgstr "خمسٌ وعشرون دقيقة حتّى السادسة" + +#: package/contents/ui/FuzzyClock.qml:91 +#, kde-format +msgid "Twenty to six" +msgstr "عشرون دقيقة حتّى السادسة" + +#: package/contents/ui/FuzzyClock.qml:92 +#, kde-format +msgid "Quarter to six" +msgstr "ربعُ ساعة حتّى السادسة" + +#: package/contents/ui/FuzzyClock.qml:93 +#, kde-format +msgid "Ten to six" +msgstr "عشرُ دقائق حتّى السادسة" + +#: package/contents/ui/FuzzyClock.qml:94 +#, kde-format +msgid "Five to six" +msgstr "خمسُ دقائق حتّى السادسة" + +#: package/contents/ui/FuzzyClock.qml:95 +#, kde-format +msgid "Six o’clock" +msgstr "الساعة السادسة" + +#: package/contents/ui/FuzzyClock.qml:96 +#, kde-format +msgid "Five past six" +msgstr "خمسُ دقائق بعد السادسة" + +#: package/contents/ui/FuzzyClock.qml:97 +#, kde-format +msgid "Ten past six" +msgstr "عشرُ دقائق بعد السادسة" + +#: package/contents/ui/FuzzyClock.qml:98 +#, kde-format +msgid "Quarter past six" +msgstr "ربعُ ساعة بعد السادسة" + +#: package/contents/ui/FuzzyClock.qml:99 +#, kde-format +msgid "Twenty past six" +msgstr "عشرون دقيقة بعد السادسة" + +#: package/contents/ui/FuzzyClock.qml:100 +#, kde-format +msgid "Twenty-five past six" +msgstr "خمسٌ وعشرون دقيقة بعد السادسة" + +#: package/contents/ui/FuzzyClock.qml:101 +#, kde-format +msgid "Half past six" +msgstr "نصفُ ساعة بعد السادسة" + +#: package/contents/ui/FuzzyClock.qml:102 +#, kde-format +msgid "Twenty-five to seven" +msgstr "خمسٌ وعشرون دقيقة حتّى السابعة" + +#: package/contents/ui/FuzzyClock.qml:103 +#, kde-format +msgid "Twenty to seven" +msgstr "عشرون دقيقة حتّى السابعة" + +#: package/contents/ui/FuzzyClock.qml:104 +#, kde-format +msgid "Quarter to seven" +msgstr "ربعُ ساعة حتّى السابعة" + +#: package/contents/ui/FuzzyClock.qml:105 +#, kde-format +msgid "Ten to seven" +msgstr "عشرُ دقائق حتّى السابعة" + +#: package/contents/ui/FuzzyClock.qml:106 +#, kde-format +msgid "Five to seven" +msgstr "خمسُ دقائق حتّى السابعة" + +#: package/contents/ui/FuzzyClock.qml:107 +#, kde-format +msgid "Seven o’clock" +msgstr "الساعة السابعة" + +#: package/contents/ui/FuzzyClock.qml:108 +#, kde-format +msgid "Five past seven" +msgstr "خمسُ دقائق بعد السابعة" + +#: package/contents/ui/FuzzyClock.qml:109 +#, kde-format +msgid "Ten past seven" +msgstr "عشرُ دقائق بعد السابعة" + +#: package/contents/ui/FuzzyClock.qml:110 +#, kde-format +msgid "Quarter past seven" +msgstr "ربعُ ساعة بعد السابعة" + +#: package/contents/ui/FuzzyClock.qml:111 +#, kde-format +msgid "Twenty past seven" +msgstr "عشرون دقيقة بعد السابعة" + +#: package/contents/ui/FuzzyClock.qml:112 +#, kde-format +msgid "Twenty-five past seven" +msgstr "خمسٌ وعشرون دقيقة بعد السابعة" + +#: package/contents/ui/FuzzyClock.qml:113 +#, kde-format +msgid "Half past seven" +msgstr "نصفُ ساعة بعد السابعة" + +#: package/contents/ui/FuzzyClock.qml:114 +#, kde-format +msgid "Twenty-five to eight" +msgstr "خمسٌ وعشرون دقيقة حتّى الثامنة" + +#: package/contents/ui/FuzzyClock.qml:115 +#, kde-format +msgid "Twenty to eight" +msgstr "عشرون دقيقة حتّى الثامنة" + +#: package/contents/ui/FuzzyClock.qml:116 +#, kde-format +msgid "Quarter to eight" +msgstr "ربعُ ساعة حتّى الثامنة" + +#: package/contents/ui/FuzzyClock.qml:117 +#, kde-format +msgid "Ten to eight" +msgstr "عشرُ دقائق حتّى الثامنة" + +#: package/contents/ui/FuzzyClock.qml:118 +#, kde-format +msgid "Five to eight" +msgstr "خمسُ دقائق حتّى الثامنة" + +#: package/contents/ui/FuzzyClock.qml:119 +#, kde-format +msgid "Eight o’clock" +msgstr "الساعة الثامنة" + +#: package/contents/ui/FuzzyClock.qml:120 +#, kde-format +msgid "Five past eight" +msgstr "خمسُ دقائق بعد الثامنة" + +#: package/contents/ui/FuzzyClock.qml:121 +#, kde-format +msgid "Ten past eight" +msgstr "عشرُ دقائق بعد الثامنة" + +#: package/contents/ui/FuzzyClock.qml:122 +#, kde-format +msgid "Quarter past eight" +msgstr "ربعُ ساعة بعد الثامنة" + +#: package/contents/ui/FuzzyClock.qml:123 +#, kde-format +msgid "Twenty past eight" +msgstr "عشرون دقيقة بعد الثامنة" + +#: package/contents/ui/FuzzyClock.qml:124 +#, kde-format +msgid "Twenty-five past eight" +msgstr "خمسٌ وعشرون دقيقة بعد الثامنة" + +#: package/contents/ui/FuzzyClock.qml:125 +#, kde-format +msgid "Half past eight" +msgstr "نصفُ ساعة بعد الثامنة" + +#: package/contents/ui/FuzzyClock.qml:126 +#, kde-format +msgid "Twenty-five to nine" +msgstr "خمسٌ وعشرون دقيقة حتّى التاسعة" + +#: package/contents/ui/FuzzyClock.qml:127 +#, kde-format +msgid "Twenty to nine" +msgstr "عشرون دقيقة حتّى التاسعة" + +#: package/contents/ui/FuzzyClock.qml:128 +#, kde-format +msgid "Quarter to nine" +msgstr "ربعُ ساعة حتّى التاسعة" + +#: package/contents/ui/FuzzyClock.qml:129 +#, kde-format +msgid "Ten to nine" +msgstr "عشرُ دقائق حتّى التاسعة" + +#: package/contents/ui/FuzzyClock.qml:130 +#, kde-format +msgid "Five to nine" +msgstr "خمسُ دقائق حتّى التاسعة" + +#: package/contents/ui/FuzzyClock.qml:131 +#, kde-format +msgid "Nine o’clock" +msgstr "الساعة التاسعة" + +#: package/contents/ui/FuzzyClock.qml:132 +#, kde-format +msgid "Five past nine" +msgstr "خمسُ دقائق بعد التاسعة" + +#: package/contents/ui/FuzzyClock.qml:133 +#, kde-format +msgid "Ten past nine" +msgstr "عشرُ دقائق بعد التاسعة" + +#: package/contents/ui/FuzzyClock.qml:134 +#, kde-format +msgid "Quarter past nine" +msgstr "ربعُ ساعة بعد التاسعة" + +#: package/contents/ui/FuzzyClock.qml:135 +#, kde-format +msgid "Twenty past nine" +msgstr "عشرون دقيقة بعد التاسعة" + +#: package/contents/ui/FuzzyClock.qml:136 +#, kde-format +msgid "Twenty-five past nine" +msgstr "خمسٌ وعشرون دقيقة بعد التاسعة" + +#: package/contents/ui/FuzzyClock.qml:137 +#, kde-format +msgid "Half past nine" +msgstr "نصفُ ساعة بعد التاسعة" + +#: package/contents/ui/FuzzyClock.qml:138 +#, kde-format +msgid "Twenty-five to ten" +msgstr "خمسٌ وعشرون دقيقة حتّى العاشرة" + +#: package/contents/ui/FuzzyClock.qml:139 +#, kde-format +msgid "Twenty to ten" +msgstr "عشرون دقيقة حتّى العاشرة" + +#: package/contents/ui/FuzzyClock.qml:140 +#, kde-format +msgid "Quarter to ten" +msgstr "ربعُ ساعة حتّى العاشرة" + +#: package/contents/ui/FuzzyClock.qml:141 +#, kde-format +msgid "Ten to ten" +msgstr "عشرُ دقائق حتّى العاشرة" + +#: package/contents/ui/FuzzyClock.qml:142 +#, kde-format +msgid "Five to ten" +msgstr "خمسُ دقائق حتّى العاشرة" + +#: package/contents/ui/FuzzyClock.qml:143 +#, kde-format +msgid "Ten o’clock" +msgstr "الساعة العاشرة" + +#: package/contents/ui/FuzzyClock.qml:144 +#, kde-format +msgid "Five past ten" +msgstr "خمسُ دقائق بعد العاشرة" + +#: package/contents/ui/FuzzyClock.qml:145 +#, kde-format +msgid "Ten past ten" +msgstr "عشرُ دقائق بعد العاشرة" + +#: package/contents/ui/FuzzyClock.qml:146 +#, kde-format +msgid "Quarter past ten" +msgstr "ربعُ ساعة بعد العاشرة" + +#: package/contents/ui/FuzzyClock.qml:147 +#, kde-format +msgid "Twenty past ten" +msgstr "عشرون دقيقة بعد العاشرة" + +#: package/contents/ui/FuzzyClock.qml:148 +#, kde-format +msgid "Twenty-five past ten" +msgstr "خمسٌ وعشرون دقيقة بعد العاشرة" + +#: package/contents/ui/FuzzyClock.qml:149 +#, kde-format +msgid "Half past ten" +msgstr "نصفُ ساعة بعد العاشرة" + +#: package/contents/ui/FuzzyClock.qml:150 +#, kde-format +msgid "Twenty-five to eleven" +msgstr "خمسٌ وعشرون دقيقة حتّى الحادية عشرة" + +#: package/contents/ui/FuzzyClock.qml:151 +#, kde-format +msgid "Twenty to eleven" +msgstr "عشرون دقيقة حتّى الحادية عشرة" + +#: package/contents/ui/FuzzyClock.qml:152 +#, kde-format +msgid "Quarter to eleven" +msgstr "ربعُ ساعة حتّى الحادية عشرة" + +#: package/contents/ui/FuzzyClock.qml:153 +#, kde-format +msgid "Ten to eleven" +msgstr "عشرُ دقائق حتّى الحادية عشرة" + +#: package/contents/ui/FuzzyClock.qml:154 +#, kde-format +msgid "Five to eleven" +msgstr "خمسُ دقائق حتّى الحادية عشرة" + +#: package/contents/ui/FuzzyClock.qml:155 +#, kde-format +msgid "Eleven o’clock" +msgstr "الساعة الحادية عشرة" + +#: package/contents/ui/FuzzyClock.qml:156 +#, kde-format +msgid "Five past eleven" +msgstr "خمسُ دقائق بعد الحادية عشرة" + +#: package/contents/ui/FuzzyClock.qml:157 +#, kde-format +msgid "Ten past eleven" +msgstr "عشرُ دقائق بعد الحادية عشرة" + +#: package/contents/ui/FuzzyClock.qml:158 +#, kde-format +msgid "Quarter past eleven" +msgstr "ربعُ ساعة بعد الحادية عشرة" + +#: package/contents/ui/FuzzyClock.qml:159 +#, kde-format +msgid "Twenty past eleven" +msgstr "عشرون دقيقة بعد الحادية عشرة" + +#: package/contents/ui/FuzzyClock.qml:160 +#, kde-format +msgid "Twenty-five past eleven" +msgstr "خمسٌ وعشرون دقيقة بعد الحادية عشرة" + +#: package/contents/ui/FuzzyClock.qml:161 +#, kde-format +msgid "Half past eleven" +msgstr "نصفُ ساعة بعد الحادية عشرة" + +#: package/contents/ui/FuzzyClock.qml:162 +#, kde-format +msgid "Twenty-five to twelve" +msgstr "خمسٌ وعشرون دقيقة حتّى الثانية عشرة" + +#: package/contents/ui/FuzzyClock.qml:163 +#, kde-format +msgid "Twenty to twelve" +msgstr "عشرون دقيقة حتّى الثانية عشرة" + +#: package/contents/ui/FuzzyClock.qml:164 +#, kde-format +msgid "Quarter to twelve" +msgstr "ربعُ ساعة حتّى الثانية عشرة" + +#: package/contents/ui/FuzzyClock.qml:165 +#, kde-format +msgid "Ten to twelve" +msgstr "عشرُ دقائق حتّى الثانية عشرة" + +#: package/contents/ui/FuzzyClock.qml:166 +#, kde-format +msgid "Five to twelve" +msgstr "خمسُ دقائق حتّى الثانية عشرة" + +#: package/contents/ui/FuzzyClock.qml:167 +#, kde-format +msgid "Twelve o’clock" +msgstr "الساعة الثانية عشرة" + +#: package/contents/ui/FuzzyClock.qml:168 +#, kde-format +msgid "Five past twelve" +msgstr "خمسُ دقائق بعد الثانية عشرة" + +#: package/contents/ui/FuzzyClock.qml:169 +#, kde-format +msgid "Ten past twelve" +msgstr "عشرُ دقائق بعد الثانية عشرة" + +#: package/contents/ui/FuzzyClock.qml:170 +#, kde-format +msgid "Quarter past twelve" +msgstr "ربعُ ساعة بعد الثانية عشرة" + +#: package/contents/ui/FuzzyClock.qml:171 +#, kde-format +msgid "Twenty past twelve" +msgstr "عشرون دقيقة بعد الثانية عشرة" + +#: package/contents/ui/FuzzyClock.qml:172 +#, kde-format +msgid "Twenty-five past twelve" +msgstr "خمسٌ وعشرون دقيقة بعد الثانية عشرة" + +#: package/contents/ui/FuzzyClock.qml:173 +#, kde-format +msgid "Half past twelve" +msgstr "نصفُ ساعة بعد الثانية عشرة" + +#: package/contents/ui/FuzzyClock.qml:174 +#, kde-format +msgid "Twenty-five to one" +msgstr "خمسٌ وعشرون دقيقة حتّى الواحدة" + +#: package/contents/ui/FuzzyClock.qml:175 +#, kde-format +msgid "Twenty to one" +msgstr "عشرون دقيقة حتّى الواحدة" + +#: package/contents/ui/FuzzyClock.qml:176 +#, kde-format +msgid "Quarter to one" +msgstr "ربعُ ساعة حتّى الواحدة" + +#: package/contents/ui/FuzzyClock.qml:177 +#, kde-format +msgid "Ten to one" +msgstr "عشرُ دقائق حتّى الواحدة" + +#: package/contents/ui/FuzzyClock.qml:178 +#, kde-format +msgid "Five to one" +msgstr "خمسُ دقائق حتّى الواحدة" + +#: package/contents/ui/FuzzyClock.qml:182 +#, kde-format +msgid "Sleep" +msgstr "النوم" + +#: package/contents/ui/FuzzyClock.qml:182 +#, kde-format +msgid "Breakfast" +msgstr "الفطور" + +#: package/contents/ui/FuzzyClock.qml:182 +#, kde-format +msgid "Second Breakfast" +msgstr "الفطور الثاني" + +#: package/contents/ui/FuzzyClock.qml:182 +#, kde-format +msgid "Elevenses" +msgstr "الأحادي عشر" + +#: package/contents/ui/FuzzyClock.qml:183 +#, kde-format +msgid "Lunch" +msgstr "الغداء" + +#: package/contents/ui/FuzzyClock.qml:183 +#, kde-format +msgid "Afternoon tea" +msgstr "شاي بعد الظهيرة" + +#: package/contents/ui/FuzzyClock.qml:183 +#, kde-format +msgid "Dinner" +msgstr "العشاء" + +#: package/contents/ui/FuzzyClock.qml:183 +#, kde-format +msgid "Supper" +msgstr "عشاء الثاني" + +#: package/contents/ui/FuzzyClock.qml:187 +#, kde-format +msgid "Night" +msgstr "الليل" + +#: package/contents/ui/FuzzyClock.qml:187 +#, kde-format +msgid "Early morning" +msgstr "الصباح الباكر" + +#: package/contents/ui/FuzzyClock.qml:187 +#, kde-format +msgid "Morning" +msgstr "الصباح" + +#: package/contents/ui/FuzzyClock.qml:187 +#, kde-format +msgid "Almost noon" +msgstr "الظهيرة تقريبًا" + +#: package/contents/ui/FuzzyClock.qml:188 +#, kde-format +msgid "Noon" +msgstr "الظهيرة" + +#: package/contents/ui/FuzzyClock.qml:188 +#, kde-format +msgid "Afternoon" +msgstr "بعد الظهيرة" + +#: package/contents/ui/FuzzyClock.qml:188 +#, kde-format +msgid "Evening" +msgstr "المساء" + +#: package/contents/ui/FuzzyClock.qml:188 +#, kde-format +msgid "Late evening" +msgstr "وقت متأخّر من المساء" + +#: package/contents/ui/FuzzyClock.qml:192 +#, kde-format +msgid "Start of week" +msgstr "بداية الأسبوع" + +#: package/contents/ui/FuzzyClock.qml:192 +#, kde-format +msgid "Middle of week" +msgstr "وسط الأسبوع" + +#: package/contents/ui/FuzzyClock.qml:192 +#, kde-format +msgid "End of week" +msgstr "نهاية الأسبوع" + +#: package/contents/ui/FuzzyClock.qml:192 +#, kde-format +msgid "Weekend!" +msgstr "عطلة الأسبوع!" + +#, fuzzy +#~| msgid "Appearance" +#~ msgctxt "@title:group" +#~ msgid "Appearance" +#~ msgstr "المظهر" diff --git a/po/ar/plasma_applet_org.kde.plasma.keyboardindicator.po b/po/ar/plasma_applet_org.kde.plasma.keyboardindicator.po new file mode 100644 index 00000000..c66b8846 --- /dev/null +++ b/po/ar/plasma_applet_org.kde.plasma.keyboardindicator.po @@ -0,0 +1,58 @@ +# Copyright (C) YEAR This file is copyright: +# This file is distributed under the same license as the kdeplasma-addons package. +# +# Zayed Al-Saidi , 2021, 2022, 2023. +msgid "" +msgstr "" +"Project-Id-Version: kdeplasma-addons\n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2024-01-22 00:38+0000\n" +"PO-Revision-Date: 2023-07-06 10:34+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: ar\n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" + +#: contents/config/config.qml:13 +#, kde-format +msgctxt "@title" +msgid "Keys" +msgstr "المفاتيح" + +#: contents/ui/configAppearance.qml:37 +#, kde-format +msgctxt "" +"@label show keyboard indicator when Caps Lock or Num Lock is activated" +msgid "Show when activated:" +msgstr "اعرض عن تنشيط:" + +#: contents/ui/configAppearance.qml:40 +#, kde-format +msgctxt "@option:check" +msgid "Caps Lock" +msgstr "قفل الحروف الكبيرة" + +#: contents/ui/configAppearance.qml:47 +#, kde-format +msgctxt "@option:check" +msgid "Num Lock" +msgstr "قفل الأرقام" + +#: contents/ui/main.qml:87 +#, kde-format +msgid "Caps Lock activated" +msgstr "قُفْل الحروف الكبيرة مفعل" + +#: contents/ui/main.qml:90 +#, kde-format +msgid "Num Lock activated" +msgstr "قُفْل الأرقام مفعل" + +#: contents/ui/main.qml:98 +#, kde-format +msgid "No lock keys activated" +msgstr "لا يوجد قفل مفتاح مفعل" diff --git a/po/ar/plasma_applet_org.kde.plasma.konsoleprofiles.po b/po/ar/plasma_applet_org.kde.plasma.konsoleprofiles.po new file mode 100644 index 00000000..b5765ff5 --- /dev/null +++ b/po/ar/plasma_applet_org.kde.plasma.konsoleprofiles.po @@ -0,0 +1,30 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Abdalrahim G. Fakhouri , 2014. +# Zayed Al-Saidi , 2021. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2024-01-23 00:38+0000\n" +"PO-Revision-Date: 2021-07-04 22:09+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: ar\n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" + +#: package/contents/ui/main.qml:69 +#, kde-format +msgctxt "@title" +msgid "Konsole Profiles" +msgstr "تشكيلات كونسول" + +#: package/contents/ui/main.qml:90 +#, kde-format +msgid "Arbitrary String Which Says Something" +msgstr "نصّ عشوائيّ يقول شيئًا" diff --git a/po/ar/plasma_applet_org.kde.plasma.mediaframe.po b/po/ar/plasma_applet_org.kde.plasma.mediaframe.po new file mode 100644 index 00000000..e99b4c91 --- /dev/null +++ b/po/ar/plasma_applet_org.kde.plasma.mediaframe.po @@ -0,0 +1,222 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Safa Alfulaij , 2016, 2017. +# Zayed Al-Saidi , 2021. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2024-01-23 00:38+0000\n" +"PO-Revision-Date: 2021-12-22 18:25+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: ar\n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" + +#: package/contents/config/config.qml:13 +#, kde-format +msgctxt "@title" +msgid "General" +msgstr "عامّ" + +#: package/contents/config/config.qml:18 +#, kde-format +msgctxt "@title" +msgid "Paths" +msgstr "المسارات" + +#: package/contents/ui/ConfigGeneral.qml:48 +#, kde-format +msgid "Change picture every:" +msgstr "غيّر الصّورة كلّ:" + +#: package/contents/ui/ConfigGeneral.qml:65 +#, kde-format +msgid "Hours" +msgstr "ساعة" + +#: package/contents/ui/ConfigGeneral.qml:79 +#, kde-format +msgid "Minutes" +msgstr "دقيقة" + +#: package/contents/ui/ConfigGeneral.qml:93 +#, kde-format +msgid "Seconds" +msgstr "ثوان" + +#: package/contents/ui/ConfigGeneral.qml:103 +#, kde-format +msgctxt "@label:listbox" +msgid "Image fill mode:" +msgstr "نمط ملء الصورة:" + +#: package/contents/ui/ConfigGeneral.qml:110 +#, kde-format +msgctxt "@item:inlistbox" +msgid "Stretch" +msgstr "مدّد" + +#: package/contents/ui/ConfigGeneral.qml:111 +#, kde-format +msgctxt "@info" +msgid "The image is scaled to fit the frame" +msgstr "تُحجَّم الصّورة لتلائم الإطار" + +#: package/contents/ui/ConfigGeneral.qml:115 +#, kde-format +msgctxt "@item:inlistbox" +msgid "Preserve aspect fit" +msgstr "أبقِ تناسب النّسبة الباعيّة" + +#: package/contents/ui/ConfigGeneral.qml:116 +#, kde-format +msgctxt "@info" +msgid "The image is scaled uniformly to fit without cropping" +msgstr "تُحجّم الصّورة بشكل موحّد دون اقتصاص" + +#: package/contents/ui/ConfigGeneral.qml:120 +#, kde-format +msgctxt "@item:inlistbox" +msgid "Preserve aspect crop" +msgstr "أبقِ اقتصاص النّسبة الباعيّة" + +#: package/contents/ui/ConfigGeneral.qml:121 +#, kde-format +msgctxt "@info" +msgid "The image is scaled uniformly to fill, cropping if necessary" +msgstr "تُحجّم الصّورة بشكل موحّد لتتناسب، وتُقتصّ إن لزم الأمر" + +#: package/contents/ui/ConfigGeneral.qml:125 +#, kde-format +msgctxt "@item:inlistbox" +msgid "Tile" +msgstr "تكرر" + +#: package/contents/ui/ConfigGeneral.qml:126 +#, kde-format +msgctxt "@info" +msgid "The image is duplicated horizontally and vertically" +msgstr "الصّورة تُكرّر أفقيًّا ورأسيًّا" + +#: package/contents/ui/ConfigGeneral.qml:130 +#, kde-format +msgctxt "@item:inlistbox" +msgid "Tile vertically" +msgstr "كرّر رأسيًّا" + +#: package/contents/ui/ConfigGeneral.qml:131 +#, kde-format +msgctxt "@info" +msgid "The image is stretched horizontally and tiled vertically" +msgstr "الصّورة تُمدّد أفقيًّا وتُكرّر رأسيًّا" + +#: package/contents/ui/ConfigGeneral.qml:135 +#, kde-format +msgctxt "@item:inlistbox" +msgid "Tile horizontally" +msgstr "كرّر أفقيًّا" + +#: package/contents/ui/ConfigGeneral.qml:136 +#, kde-format +msgctxt "@info" +msgid "The image is stretched vertically and tiled horizontally" +msgstr "الصّورة تُمدّد رأسيًّا وتُكرّر أفقيًّا" + +#: package/contents/ui/ConfigGeneral.qml:140 +#, kde-format +msgctxt "@item:inlistbox" +msgid "Pad" +msgstr "كما هي" + +#: package/contents/ui/ConfigGeneral.qml:141 +#, kde-format +msgctxt "@info" +msgid "The image is not transformed" +msgstr "الصورة لا تتحول" + +#: package/contents/ui/ConfigGeneral.qml:186 +#, kde-format +msgctxt "@label:checkbox" +msgid "General:" +msgstr "عام:" + +#: package/contents/ui/ConfigGeneral.qml:187 +#, kde-format +msgctxt "@option:check" +msgid "Randomize order" +msgstr "ترتيب عشوائي" + +#: package/contents/ui/ConfigGeneral.qml:192 +#, kde-format +msgctxt "@option:check" +msgid "Pause slideshow when cursor is over image" +msgstr "ألبث عرض الشرائح عند الحوم حول الصورة" + +#: package/contents/ui/ConfigGeneral.qml:197 +#, kde-format +msgctxt "@option:check" +msgid "Click on image to open in external application" +msgstr "النقر على الصورة يفتحها في عارض خارجي" + +#: package/contents/ui/ConfigPaths.qml:67 +#, kde-format +msgid "Remove path" +msgstr "أزل مسار" + +#: package/contents/ui/ConfigPaths.qml:76 +#, kde-format +msgctxt "@title:window" +msgid "Choose Files" +msgstr "حدد ملفات" + +#: package/contents/ui/ConfigPaths.qml:102 +#, kde-format +msgctxt "@title:window" +msgid "Choose a Folder" +msgstr "اختر مجلّدًا" + +#: package/contents/ui/ConfigPaths.qml:122 +#, kde-format +msgctxt "@action:button" +msgid "Add Folder…" +msgstr "أضف مجلدًا..." + +#: package/contents/ui/ConfigPaths.qml:128 +#, kde-format +msgctxt "@action:button" +msgid "Add Files…" +msgstr "أضف ملفات..." + +#: package/contents/ui/main.qml:452 +#, kde-format +msgctxt "@action:button" +msgid "Configure…" +msgstr "اضبط..." + +#~ msgctxt "@option:check" +#~ msgid "Show background frame" +#~ msgstr "أظهر إطار الخلفيّة" + +#, fuzzy +#~| msgid "Paths:" +#~ msgctxt "@label:listbox" +#~ msgid "Paths:" +#~ msgstr "المسارات:" + +#, fuzzy +#~| msgid "s" +#~ msgctxt "@item:valuesuffix spacing to number + unit (seconds)" +#~ msgid " s" +#~ msgstr "ثا" + +#, fuzzy +#~| msgid "Pause on mouseover" +#~ msgctxt "@option:check" +#~ msgid "Pause on mouse-over" +#~ msgstr "ألبث عند وضع المؤشّر" diff --git a/po/ar/plasma_applet_org.kde.plasma.notes.po b/po/ar/plasma_applet_org.kde.plasma.notes.po new file mode 100644 index 00000000..9c027669 --- /dev/null +++ b/po/ar/plasma_applet_org.kde.plasma.notes.po @@ -0,0 +1,239 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Safa Alfulaij , 2014, 2015. +# SPDX-FileCopyrightText: 2021, 2022, 2023, 2024 Zayed Al-Saidi +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2023-12-26 01:34+0000\n" +"PO-Revision-Date: 2024-01-17 17:37+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: ar\n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" + +#: package/contents/config/config.qml:13 +#, kde-format +msgctxt "@title" +msgid "Appearance" +msgstr "المظهر" + +#: package/contents/ui/configAppearance.qml:31 +#, kde-format +msgid "%1pt" +msgstr "%1نقطة" + +#: package/contents/ui/configAppearance.qml:37 +#, kde-format +msgid "Text font size:" +msgstr "حجم الخط:" + +#: package/contents/ui/configAppearance.qml:62 +#, kde-format +msgid "A white sticky note" +msgstr "ملاحظة بيضاء لاصقة" + +#: package/contents/ui/configAppearance.qml:63 +#, kde-format +msgid "A black sticky note" +msgstr "ملاحظة سوداء" + +#: package/contents/ui/configAppearance.qml:64 +#, kde-format +msgid "A red sticky note" +msgstr "ملاحظة حمراء لاصقة" + +#: package/contents/ui/configAppearance.qml:65 +#, kde-format +msgid "An orange sticky note" +msgstr "ملاحظة برتقاليّة لاصقة" + +#: package/contents/ui/configAppearance.qml:66 +#, kde-format +msgid "A yellow sticky note" +msgstr "ملاحظة صفراء لاصقة" + +#: package/contents/ui/configAppearance.qml:67 +#, kde-format +msgid "A green sticky note" +msgstr "ملاحظة خضراء لاصقة" + +#: package/contents/ui/configAppearance.qml:68 +#, kde-format +msgid "A blue sticky note" +msgstr "ملاحظة زرقاء لاصقة" + +#: package/contents/ui/configAppearance.qml:69 +#, kde-format +msgid "A pink sticky note" +msgstr "ملاحظة ورديّة لاصقة" + +#: package/contents/ui/configAppearance.qml:70 +#, kde-format +msgid "A transparent sticky note" +msgstr "ملاحظة شفّافة لاصقة" + +#: package/contents/ui/configAppearance.qml:71 +#, kde-format +msgid "A transparent sticky note with light text" +msgstr "ملاحظة شفّافة لاصقة مع نص فاتح" + +#: package/contents/ui/main.qml:267 +#, kde-format +msgid "Undo" +msgstr "تراجع" + +#: package/contents/ui/main.qml:275 +#, kde-format +msgid "Redo" +msgstr "كرّر" + +#: package/contents/ui/main.qml:285 +#, kde-format +msgid "Cut" +msgstr "قصّ" + +#: package/contents/ui/main.qml:293 +#, kde-format +msgid "Copy" +msgstr "انسخ" + +#: package/contents/ui/main.qml:301 +#, kde-format +msgid "Paste" +msgstr "ألصق" + +#: package/contents/ui/main.qml:307 +#, kde-format +msgid "Paste with Full Formatting" +msgstr "ألصِق بكامل التنسيق" + +#: package/contents/ui/main.qml:314 +#, kde-format +msgctxt "@action:inmenu" +msgid "Remove Formatting" +msgstr "أزل التنسيق" + +#: package/contents/ui/main.qml:329 +#, kde-format +msgid "Delete" +msgstr "احذف" + +#: package/contents/ui/main.qml:336 +#, kde-format +msgid "Clear" +msgstr "امسح" + +#: package/contents/ui/main.qml:346 +#, kde-format +msgid "Select All" +msgstr "حدّد الكلّ" + +#: package/contents/ui/main.qml:474 +#, kde-format +msgctxt "@info:tooltip" +msgid "Bold" +msgstr "ثخين" + +#: package/contents/ui/main.qml:486 +#, kde-format +msgctxt "@info:tooltip" +msgid "Italic" +msgstr "مائل" + +#: package/contents/ui/main.qml:498 +#, kde-format +msgctxt "@info:tooltip" +msgid "Underline" +msgstr "تسطير" + +#: package/contents/ui/main.qml:510 +#, kde-format +msgctxt "@info:tooltip" +msgid "Strikethrough" +msgstr "مشطوب" + +#: package/contents/ui/main.qml:584 +#, kde-format +msgid "Discard this note?" +msgstr "أأهمل هذه الملاحظة؟" + +#: package/contents/ui/main.qml:585 +#, kde-format +msgid "Are you sure you want to discard this note?" +msgstr "هل أنت متأكد أنك تريد إهمال هذه الملاحظة؟ " + +#: package/contents/ui/main.qml:605 +#, kde-format +msgctxt "@item:inmenu" +msgid "White" +msgstr "الأبيض" + +#: package/contents/ui/main.qml:609 +#, kde-format +msgctxt "@item:inmenu" +msgid "Black" +msgstr "الأسود" + +#: package/contents/ui/main.qml:613 +#, kde-format +msgctxt "@item:inmenu" +msgid "Red" +msgstr "الأحمر" + +#: package/contents/ui/main.qml:617 +#, kde-format +msgctxt "@item:inmenu" +msgid "Orange" +msgstr "البرتقالي" + +#: package/contents/ui/main.qml:621 +#, kde-format +msgctxt "@item:inmenu" +msgid "Yellow" +msgstr "الأصفر" + +#: package/contents/ui/main.qml:625 +#, kde-format +msgctxt "@item:inmenu" +msgid "Green" +msgstr "الأخضر" + +#: package/contents/ui/main.qml:629 +#, kde-format +msgctxt "@item:inmenu" +msgid "Blue" +msgstr "الأزرق" + +#: package/contents/ui/main.qml:633 +#, kde-format +msgctxt "@item:inmenu" +msgid "Pink" +msgstr "الوردي" + +#: package/contents/ui/main.qml:637 +#, kde-format +msgctxt "@item:inmenu" +msgid "Transparent" +msgstr "شفّاف" + +#: package/contents/ui/main.qml:641 +#, kde-format +msgctxt "@item:inmenu" +msgid "Transparent Light" +msgstr "شفّاف فاتح" + +#~ msgid "Background color" +#~ msgstr "لون الخلفية" + +#~ msgid "Toggle text format options" +#~ msgstr "اقلب خيارات تنسيق النّصّ" + +#~ msgid "Notes Settings..." +#~ msgstr "إعدادات الملاحظات..." diff --git a/po/ar/plasma_applet_org.kde.plasma.private.grouping.po b/po/ar/plasma_applet_org.kde.plasma.private.grouping.po new file mode 100644 index 00000000..b12c9098 --- /dev/null +++ b/po/ar/plasma_applet_org.kde.plasma.private.grouping.po @@ -0,0 +1,23 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Safa Alfulaij , 2017. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2023-07-29 01:59+0000\n" +"PO-Revision-Date: 2017-02-14 20:19+0300\n" +"Last-Translator: Safa Alfulaij \n" +"Language-Team: Arabic \n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" + +#: package/contents/ui/main.qml:146 +#, kde-format +msgid "Drag applets here" +msgstr "أفلت البريمجات هنا" diff --git a/po/ar/plasma_applet_org.kde.plasma.quicklaunch.po b/po/ar/plasma_applet_org.kde.plasma.quicklaunch.po new file mode 100644 index 00000000..fe09dda2 --- /dev/null +++ b/po/ar/plasma_applet_org.kde.plasma.quicklaunch.po @@ -0,0 +1,135 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Safa Alfulaij , 2016. +# Zayed Al-Saidi , 2021, 2022. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2024-01-23 00:38+0000\n" +"PO-Revision-Date: 2022-02-03 22:24+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: ar\n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" + +#: package/contents/config/config.qml:12 +#, kde-format +msgctxt "@title" +msgid "General" +msgstr "عامّ" + +#: package/contents/ui/ConfigGeneral.qml:27 +#, kde-format +msgctxt "@label:spinbox" +msgid "Maximum columns:" +msgstr "أقصى عدد للأعمدة:" + +#: package/contents/ui/ConfigGeneral.qml:27 +#, kde-format +msgctxt "@label:spinbox" +msgid "Maximum rows:" +msgstr "أقصى عدد للصّفوف:" + +#: package/contents/ui/ConfigGeneral.qml:39 +#, kde-format +msgctxt "@title:group" +msgid "Appearance:" +msgstr "المظهر:" + +#: package/contents/ui/ConfigGeneral.qml:41 +#, kde-format +msgctxt "@option:check" +msgid "Show launcher names" +msgstr "أظهر أسماء المطلقات" + +#: package/contents/ui/ConfigGeneral.qml:46 +#, kde-format +msgctxt "@option:check" +msgid "Enable popup" +msgstr "فعّل المنبثقة" + +#: package/contents/ui/ConfigGeneral.qml:56 +#, kde-format +msgctxt "@title:group" +msgid "Title:" +msgstr "العنوان:" + +#: package/contents/ui/ConfigGeneral.qml:64 +#, kde-format +msgctxt "@option:check" +msgid "Show:" +msgstr "أظهر:" + +#: package/contents/ui/ConfigGeneral.qml:80 +#, kde-format +msgctxt "@info:placeholder" +msgid "Custom title" +msgstr "عنوان مخصّص" + +#: package/contents/ui/IconItem.qml:177 +#, kde-format +msgid "Launch %1" +msgstr "شغّل %1" + +#: package/contents/ui/IconItem.qml:261 +#, kde-format +msgctxt "@action:inmenu" +msgid "Add Launcher…" +msgstr "أضف مطلقًا..." + +#: package/contents/ui/IconItem.qml:267 +#, kde-format +msgctxt "@action:inmenu" +msgid "Edit Launcher…" +msgstr "حرّر المطلق..." + +#: package/contents/ui/IconItem.qml:273 +#, kde-format +msgctxt "@action:inmenu" +msgid "Remove Launcher" +msgstr "أزل المطلق" + +#: package/contents/ui/main.qml:147 +#, kde-format +msgid "Quicklaunch" +msgstr "الإطلاق السّريع" + +#: package/contents/ui/main.qml:148 +#, kde-format +msgctxt "@info" +msgid "Add launchers by Drag and Drop or by using the context menu." +msgstr "أضف مطلقات بسحبها وإفلاتها أو باستخدام قائمة السّياق." + +#: package/contents/ui/main.qml:178 +#, kde-format +msgid "Hide icons" +msgstr "أخفِ الأيقونات" + +#: package/contents/ui/main.qml:178 +#, kde-format +msgid "Show hidden icons" +msgstr "أظهر الأيقونات المخفيّة" + +#: package/contents/ui/main.qml:281 +#, kde-format +msgctxt "@action" +msgid "Add Launcher…" +msgstr "أضف مطلقًا..." + +#, fuzzy +#~| msgid "Arrangement" +#~ msgctxt "@title:group" +#~ msgid "Arrangement" +#~ msgstr "التّرتيب" + +#, fuzzy +#~| msgid "Enter title" +#~ msgctxt "@info:placeholder" +#~ msgid "Enter title" +#~ msgstr "أدخل عنوانا" diff --git a/po/ar/plasma_applet_org.kde.plasma.timer.po b/po/ar/plasma_applet_org.kde.plasma.timer.po new file mode 100644 index 00000000..4f5b889c --- /dev/null +++ b/po/ar/plasma_applet_org.kde.plasma.timer.po @@ -0,0 +1,244 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Safa Alfulaij , 2014, 2015. +# Zayed Al-Saidi , 2021, 2022. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2024-01-23 00:38+0000\n" +"PO-Revision-Date: 2022-12-22 11:00+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: ar\n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" + +#: package/contents/config/config.qml:13 +#, kde-format +msgctxt "@title" +msgid "Appearance" +msgstr "المظهر" + +#: package/contents/config/config.qml:19 +#, kde-format +msgctxt "@title" +msgid "Predefined Timers" +msgstr "مؤقتات مسبقة التعريف" + +#: package/contents/config/config.qml:24 +#, kde-format +msgctxt "@title" +msgid "Advanced" +msgstr "متقدّم" + +#: package/contents/ui/CompactRepresentation.qml:135 +#, kde-format +msgctxt "@action:button" +msgid "Pause Timer" +msgstr "ألبث المؤقّت" + +#: package/contents/ui/CompactRepresentation.qml:135 +#, kde-format +msgctxt "@action:button" +msgid "Start Timer" +msgstr "ابدأ المؤقت" + +#: package/contents/ui/CompactRepresentation.qml:198 +#: package/contents/ui/CompactRepresentation.qml:216 +#, kde-format +msgctxt "remaining time" +msgid "%1s" +msgid_plural "%1s" +msgstr[0] "%1ث" +msgstr[1] "%1ث" +msgstr[2] "%1ث" +msgstr[3] "%1ث" +msgstr[4] "%1ث" +msgstr[5] "%1ث" + +#: package/contents/ui/configAdvanced.qml:22 +#, kde-format +msgctxt "@title:label" +msgid "After timer completes:" +msgstr "بعد اكتمال المؤقت:" + +#: package/contents/ui/configAdvanced.qml:26 +#, kde-format +msgctxt "@option:check" +msgid "Execute command:" +msgstr "نفّذ أمرًا:" + +#: package/contents/ui/configAppearance.qml:30 +#, kde-format +msgctxt "@title:label" +msgid "Display:" +msgstr "العرض:" + +#: package/contents/ui/configAppearance.qml:35 +#, kde-format +msgctxt "@option:check" +msgid "Show title:" +msgstr "أظهر عنوان:" + +#: package/contents/ui/configAppearance.qml:52 +#, kde-format +msgctxt "@option:check" +msgid "Show remaining time" +msgstr "أظهر الوقت المتبقي" + +#: package/contents/ui/configAppearance.qml:58 +#, kde-format +msgctxt "@option:check" +msgid "Show seconds" +msgstr "أظهر الثّواني" + +#: package/contents/ui/configAppearance.qml:63 +#, kde-format +msgctxt "@option:check" +msgid "Show timer toggle" +msgstr "أظهر مبدل المؤقت" + +#: package/contents/ui/configAppearance.qml:68 +#, kde-format +msgctxt "@option:check" +msgid "Show progress bar" +msgstr "أظهر شريط التقدم" + +#: package/contents/ui/configAppearance.qml:78 +#, kde-format +msgctxt "@title:label" +msgid "Notifications:" +msgstr "الإخطارات:" + +#: package/contents/ui/configAppearance.qml:82 +#, kde-format +msgctxt "@option:check" +msgid "Show notification text:" +msgstr "أظهر إخطارًا" + +#: package/contents/ui/configTimes.qml:71 +#, kde-format +msgid "" +"If you add predefined timers here, they will appear in plasmoid context menu." +msgstr "" +"إذا قمت بإضافة مؤقتات محددة مسبقًا هنا ، فسوف تظهر في قائمة سياق بلازميد." + +#: package/contents/ui/configTimes.qml:78 +#, kde-format +msgid "Add" +msgstr "أضف" + +#: package/contents/ui/configTimes.qml:119 +#, kde-format +msgid "Scroll over digits to change time" +msgstr "مرر بالفأرة على الأرقام لتغيير الوقت" + +#: package/contents/ui/configTimes.qml:126 +#, kde-format +msgid "Apply" +msgstr "طبّق" + +#: package/contents/ui/configTimes.qml:134 +#, kde-format +msgid "Cancel" +msgstr "ألغِ" + +#: package/contents/ui/configTimes.qml:143 +#, kde-format +msgid "Edit" +msgstr "حرّر" + +#: package/contents/ui/configTimes.qml:152 +#, kde-format +msgid "Delete" +msgstr "احذف" + +#: package/contents/ui/main.qml:67 +#, kde-format +msgid "%1 is running" +msgstr "%1 يعمل" + +#: package/contents/ui/main.qml:69 +#, kde-format +msgid "%1 not running" +msgstr "%1 لا يعمل" + +#: package/contents/ui/main.qml:72 +#, kde-format +msgid "Remaining time left: %1 second" +msgid_plural "Remaining time left: %1 seconds" +msgstr[0] "الوقت المتبقّي: أقلّ من ثانية" +msgstr[1] "الوقت المتبقّي: ثانية واحدة" +msgstr[2] "الوقت المتبقّي: ثانيتين" +msgstr[3] "الوقت المتبقّي: %1 ثوان" +msgstr[4] "الوقت المتبقّي: %1 ثانية" +msgstr[5] "الوقت المتبقّي: %1 ثانية" + +#: package/contents/ui/main.qml:72 +#, kde-format +msgid "" +"Use mouse wheel to change digits or choose from predefined timers in the " +"context menu" +msgstr "" +"استخدم عجلة الفأرة لتغيير الأرقام أو اختر أحد المؤقّتات المعرّفة مسبقًا في " +"قائمة السّياق" + +#: package/contents/ui/main.qml:89 +#, kde-format +msgid "Timer" +msgstr "المؤقّت" + +#: package/contents/ui/main.qml:90 +#, kde-format +msgid "Timer finished" +msgstr "انتهى المؤقّت" + +#: package/contents/ui/main.qml:130 +#, kde-format +msgctxt "@action" +msgid "&Start" +msgstr "ا&بدأ" + +#: package/contents/ui/main.qml:135 +#, kde-format +msgctxt "@action" +msgid "S&top" +msgstr "أو&قِف" + +#: package/contents/ui/main.qml:140 +#, kde-format +msgctxt "@action" +msgid "&Reset" +msgstr "&صفّر" + +#~ msgid "Timer is running" +#~ msgstr "المؤقّت يعمل" + +#, fuzzy +#~| msgid "Run command" +#~ msgctxt "@title:group" +#~ msgid "Run Command" +#~ msgstr "شغّل أمرًا" + +#, fuzzy +#~| msgid "Command:" +#~ msgctxt "@label:textbox" +#~ msgid "Command:" +#~ msgstr "الأمر:" + +#, fuzzy +#~| msgid "Title:" +#~ msgctxt "@label:textbox" +#~ msgid "Title:" +#~ msgstr "العنوان:" + +#, fuzzy +#~| msgid "Text:" +#~ msgctxt "@label:textbox" +#~ msgid "Text:" +#~ msgstr "النّصّ:" diff --git a/po/ar/plasma_applet_org.kde.plasma.userswitcher.po b/po/ar/plasma_applet_org.kde.plasma.userswitcher.po new file mode 100644 index 00000000..7bb36841 --- /dev/null +++ b/po/ar/plasma_applet_org.kde.plasma.userswitcher.po @@ -0,0 +1,164 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# Safa Alfulaij , 2016, 2017. +# SPDX-FileCopyrightText: 2021, 2022, 2024 Zayed Al-Saidi +# +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2024-10-29 02:31+0000\n" +"PO-Revision-Date: 2024-09-05 09:36+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: ar\n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" +"X-Generator: Lokalize 23.08.5\n" + +#: package/contents/config/config.qml:11 +#, kde-format +msgctxt "@title" +msgid "General" +msgstr "عامّ" + +#: package/contents/ui/configGeneral.qml:27 +#, kde-format +msgctxt "@title:label" +msgid "Username style:" +msgstr "نمط اسم المستخدم:" + +#: package/contents/ui/configGeneral.qml:30 +#, kde-format +msgctxt "@option:radio" +msgid "Full name (if available)" +msgstr "الاسم الكامل (إن كان متوفّرًا)" + +#: package/contents/ui/configGeneral.qml:37 +#, kde-format +msgctxt "@option:radio" +msgid "Login username" +msgstr "اسم الولوج" + +#: package/contents/ui/configGeneral.qml:55 +#, kde-format +msgctxt "@title:label" +msgid "Show:" +msgstr "أظهر:" + +#: package/contents/ui/configGeneral.qml:58 +#, kde-format +msgctxt "@option:radio" +msgid "Name" +msgstr "الاسم" + +#: package/contents/ui/configGeneral.qml:72 +#, kde-format +msgctxt "@option:radio" +msgid "User picture" +msgstr "استخدم صورة" + +#: package/contents/ui/configGeneral.qml:86 +#, kde-format +msgctxt "@option:radio" +msgid "Name and user picture" +msgstr "الاسم وصورة المستخدم" + +#: package/contents/ui/configGeneral.qml:105 +#, kde-format +msgctxt "@title:label" +msgid "Advanced:" +msgstr "متقدّم:" + +#: package/contents/ui/configGeneral.qml:107 +#, kde-format +msgctxt "@option:check" +msgid "Show technical session information" +msgstr "أظهر المعلومات التّقنيّة عن الجلسات" + +#: package/contents/ui/main.qml:47 +#, kde-format +msgid "You are logged in as %1" +msgstr "أنت والج ك‍ %1" + +#: package/contents/ui/main.qml:142 +#, kde-format +msgid "Current user" +msgstr "المستخدم الحاليّ" + +#: package/contents/ui/main.qml:171 +#, kde-format +msgctxt "Nobody logged in on that session" +msgid "Unused" +msgstr "غير مستخدمة" + +#: package/contents/ui/main.qml:191 +#, kde-format +msgctxt "User logged in on console number" +msgid "TTY %1" +msgstr "الطّرفيّة %1" + +#: package/contents/ui/main.qml:193 +#, kde-format +msgctxt "User logged in on console (X display number)" +msgid "on %1 (%2)" +msgstr "على %1 (%2)" + +#: package/contents/ui/main.qml:201 +#, kde-format +msgctxt "@action:button" +msgid "Switch to User %1" +msgstr "بدّل إلى المستخدم %1" + +#: package/contents/ui/main.qml:210 +#, kde-format +msgctxt "@action" +msgid "New Session" +msgstr "جلسة جديدة" + +#: package/contents/ui/main.qml:222 +#, kde-format +msgctxt "@action" +msgid "Lock Screen" +msgstr "اقفل الشّاشة" + +#: package/contents/ui/main.qml:234 +#, kde-format +msgctxt "Show a dialog with options to logout/shutdown/restart" +msgid "Show Logout Screen" +msgstr "أظهر شاشة الخروج" + +#~ msgctxt "Show a dialog with options to logout/shutdown/restart" +#~ msgid "Log Out" +#~ msgstr "اخرج" + +#~ msgctxt "Show a dialog with options to logout/shutdown/restart" +#~ msgid "Leave…" +#~ msgstr "اترك..." + +#, fuzzy +#~| msgid "Layout" +#~ msgctxt "@title:group" +#~ msgid "Layout" +#~ msgstr "التّخطيط" + +#, fuzzy +#~| msgid "Show only name" +#~ msgctxt "@option:radio" +#~ msgid "Show only name" +#~ msgstr "أظهر الاسم فقط" + +#, fuzzy +#~| msgid "Show only avatar" +#~ msgctxt "@option:radio" +#~ msgid "Show only avatar" +#~ msgstr "أظهر الصّورة فقط" + +#, fuzzy +#~| msgid "Show both avatar and name" +#~ msgctxt "@option:radio" +#~ msgid "Show both avatar and name" +#~ msgstr "أظهر الاسم مع الصّورة" diff --git a/po/ar/plasma_applet_org.kde.plasma.weather.po b/po/ar/plasma_applet_org.kde.plasma.weather.po new file mode 100644 index 00000000..048e6f77 --- /dev/null +++ b/po/ar/plasma_applet_org.kde.plasma.weather.po @@ -0,0 +1,842 @@ +# translation of plasma_applet_weather.po to Arabic +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# SPDX-FileCopyrightText: 2021, 2022, 2024 Zayed Al-Saidi +# OsamaKhalid , 2009. +# Safa Alfulaij , 2017. +# Zayed Al-Saidi , 2024. +msgid "" +msgstr "" +"Project-Id-Version: plasma_applet_weather\n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2024-08-30 00:40+0000\n" +"PO-Revision-Date: 2024-09-05 09:36+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: ar\n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" +"X-Generator: Lokalize 23.08.5\n" + +#: i18n.dat:1 +#, kde-format +msgctxt "wind direction" +msgid "N" +msgstr "ش" + +#: i18n.dat:2 +#, kde-format +msgctxt "wind direction" +msgid "NNE" +msgstr "ش.ش.شرق" + +#: i18n.dat:3 +#, kde-format +msgctxt "wind direction" +msgid "NE" +msgstr "ش.شرق" + +#: i18n.dat:4 +#, kde-format +msgctxt "wind direction" +msgid "ENE" +msgstr "شرق.ش.شرق" + +#: i18n.dat:5 +#, kde-format +msgctxt "wind direction" +msgid "E" +msgstr "شرق" + +#: i18n.dat:6 +#, kde-format +msgctxt "wind direction" +msgid "SSE" +msgstr "ج.ج.شرق" + +#: i18n.dat:7 +#, kde-format +msgctxt "wind direction" +msgid "SE" +msgstr "ج.شرق" + +#: i18n.dat:8 +#, kde-format +msgctxt "wind direction" +msgid "ESE" +msgstr "شرق.ج.شرق" + +#: i18n.dat:9 +#, kde-format +msgctxt "wind direction" +msgid "S" +msgstr "ج" + +#: i18n.dat:10 +#, kde-format +msgctxt "wind direction" +msgid "NNW" +msgstr "ش.ش.غرب" + +#: i18n.dat:11 +#, kde-format +msgctxt "wind direction" +msgid "NW" +msgstr "ش.غرب" + +#: i18n.dat:12 +#, kde-format +msgctxt "wind direction" +msgid "WNW" +msgstr "غرب.ش.غرب" + +#: i18n.dat:13 +#, kde-format +msgctxt "wind direction" +msgid "W" +msgstr "غرب" + +#: i18n.dat:14 +#, kde-format +msgctxt "wind direction" +msgid "SSW" +msgstr "ج.ج.غرب" + +#: i18n.dat:15 +#, kde-format +msgctxt "wind direction" +msgid "SW" +msgstr "ج.غرب" + +#: i18n.dat:16 +#, kde-format +msgctxt "wind direction" +msgid "WSW" +msgstr "غرب.ج.غرب" + +#: i18n.dat:17 +#, kde-format +msgctxt "wind direction" +msgid "VR" +msgstr "متغير" + +#: i18n.dat:18 +#, kde-format +msgctxt "wind speed" +msgid "Calm" +msgstr "هادئ" + +#: package/contents/config/config.qml:15 +#, kde-format +msgctxt "@title" +msgid "Weather Station" +msgstr "محطّة الطّقس" + +#: package/contents/config/config.qml:21 +#, kde-format +msgctxt "@title" +msgid "Appearance" +msgstr "المظهر" + +#: package/contents/config/config.qml:31 +#, kde-format +msgctxt "@title" +msgid "Units" +msgstr "الوحدات" + +#: package/contents/ui/config/ConfigAppearance.qml:35 +#, kde-format +msgctxt "@title:group" +msgid "Compact Mode" +msgstr "وضع المُدمج" + +#: package/contents/ui/config/ConfigAppearance.qml:43 +#, kde-format +msgctxt "@label" +msgid "Show temperature:" +msgstr "أظهر درجة الحرارة:" + +#: package/contents/ui/config/ConfigAppearance.qml:46 +#, kde-format +msgctxt "@option:radio Show temperature:" +msgid "Over the widget icon" +msgstr "على أيقونة التطبيق" + +#: package/contents/ui/config/ConfigAppearance.qml:54 +#, kde-format +msgctxt "@option:radio Show temperature:" +msgid "Beside the widget icon" +msgstr "بجانب أيقونة التطبيق" + +#: package/contents/ui/config/ConfigAppearance.qml:61 +#, kde-format +msgctxt "@option:radio Show temperature:" +msgid "Do not show" +msgstr "لا تعرض" + +#: package/contents/ui/config/ConfigAppearance.qml:70 +#, kde-format +msgctxt "@label" +msgid "Show in tooltip:" +msgstr "أظهر في التلميحة:" + +#: package/contents/ui/config/ConfigAppearance.qml:71 +#, kde-format +msgctxt "@option:check" +msgid "Temperature" +msgstr "درجة الحرارة" + +#: package/contents/ui/config/ConfigAppearance.qml:76 +#, kde-format +msgctxt "@option:check Show in tooltip: wind" +msgid "Wind" +msgstr "الريح" + +#: package/contents/ui/config/ConfigAppearance.qml:81 +#, kde-format +msgctxt "@option:check Show in tooltip: pressure" +msgid "Pressure" +msgstr "الضغط" + +#: package/contents/ui/config/ConfigAppearance.qml:86 +#, kde-format +msgctxt "@option:check Show in tooltip: humidity" +msgid "Humidity" +msgstr "الرّطوبة" + +#: package/contents/ui/config/ConfigUnits.qml:32 +#, kde-format +msgctxt "@label:listbox" +msgid "Temperature:" +msgstr "درجة الحرارة:" + +#: package/contents/ui/config/ConfigUnits.qml:38 +#, kde-format +msgctxt "@label:listbox" +msgid "Pressure:" +msgstr "الضغط:" + +#: package/contents/ui/config/ConfigUnits.qml:44 +#, kde-format +msgctxt "@label:listbox" +msgid "Wind speed:" +msgstr "سرعة الرّياح:" + +#: package/contents/ui/config/ConfigUnits.qml:50 +#, kde-format +msgctxt "@label:listbox" +msgid "Visibility:" +msgstr "الظّهور:" + +#: package/contents/ui/config/ConfigWeatherStation.qml:50 +#, kde-format +msgctxt "@label:spinbox" +msgid "Update every:" +msgstr "حدّث كلّ:" + +#: package/contents/ui/config/ConfigWeatherStation.qml:53 +#, kde-format +msgid "%1 minute" +msgid_plural "%1 minutes" +msgstr[0] "دقيقة" +msgstr[1] "دقيقة" +msgstr[2] "دقيقتان" +msgstr[3] "%1 دقائق" +msgstr[4] "%1 دقيقة" +msgstr[5] "%1 دقيقة" + +#: package/contents/ui/config/ConfigWeatherStation.qml:65 +#, kde-format +msgctxt "@label" +msgid "Location:" +msgstr "المكان:" + +#: package/contents/ui/config/ConfigWeatherStation.qml:71 +#, kde-format +msgctxt "No location is currently selected" +msgid "None selected" +msgstr "غير محدد" + +#: package/contents/ui/config/ConfigWeatherStation.qml:76 +#, kde-format +msgctxt "@label" +msgid "Provider:" +msgstr "المزود:" + +#: package/contents/ui/config/ConfigWeatherStation.qml:101 +#, kde-format +msgctxt "@info:placeholder" +msgid "Enter new location" +msgstr "أدخل مكاناً جديداً" + +#: package/contents/ui/config/ConfigWeatherStation.qml:101 +#, kde-format +msgctxt "@info:placeholder" +msgid "Enter location" +msgstr "أدخل المكان" + +#: package/contents/ui/config/ConfigWeatherStation.qml:185 +#, kde-format +msgctxt "@info" +msgid "No weather stations found for '%1'" +msgstr "لم يُعثر على محطّات طقس ل‍ ’%1‘" + +#: package/contents/ui/config/ConfigWeatherStation.qml:187 +#, kde-format +msgctxt "@info" +msgid "Search for a weather station to change your location" +msgstr "ابحث عن محطة أرصاد جوية لتغير موقعك" + +#: package/contents/ui/config/ConfigWeatherStation.qml:189 +#, kde-format +msgctxt "@info" +msgid "Search for a weather station to set your location" +msgstr "ابحث عن محطة أرصاد جوية لتعين موقعك" + +#: package/contents/ui/config/ConfigWeatherStation.qml:192 +#, kde-format +msgctxt "@info:usagetip" +msgid "" +"If you've used this weather station in the past, it's possible that a server " +"outage at the weather station provider has made it temporarily unavailable. " +"Try again later." +msgstr "" +"إذا كنت قد استخدمت محطة الطقس هذه في الماضي، فمن المحتمل أن يكون انقطاع " +"الخادم لدى موفر محطة الطقس قد جعلها غير متاحة مؤقتًا.حاول مرة أخرى في وقت " +"لاحق." + +#: package/contents/ui/ForecastView.qml:45 +#, kde-format +msgctxt "Time of the day (from the duple Day/Night)" +msgid "Day" +msgstr "النهار" + +#: package/contents/ui/ForecastView.qml:49 +#, kde-format +msgctxt "Time of the day (from the duple Day/Night)" +msgid "Night" +msgstr "الليل" + +#: package/contents/ui/ForecastView.qml:105 +#, kde-format +msgctxt "certain weather condition (probability percentage)" +msgid "%1 (%2%)" +msgstr "%1 (%2٪)" + +#: package/contents/ui/ForecastView.qml:119 +#, kde-format +msgctxt "Probability of precipitation in percentage" +msgid "☂%1%" +msgstr "︎☂%1٪" + +#: package/contents/ui/ForecastView.qml:127 +#: package/contents/ui/ForecastView.qml:135 +#, kde-format +msgctxt "Short for no data available" +msgid "-" +msgstr "-" + +#: package/contents/ui/FullRepresentation.qml:37 +#, kde-format +msgid "Please set your location" +msgstr "رجاء اختر موقعك" + +#: package/contents/ui/FullRepresentation.qml:40 +#, kde-format +msgid "Set location…" +msgstr "حدد الموقع..." + +#: package/contents/ui/FullRepresentation.qml:55 +#, kde-format +msgid "Unable to retrieve weather information for %1" +msgstr "غير قادر على جلب معلومات الطقس لـ %1" + +#: package/contents/ui/FullRepresentation.qml:57 +#, kde-format +msgctxt "@info:usagetip" +msgid "" +"The network request timed out, possibly due to a server outage at the " +"weather station provider. Check again later." +msgstr "" +"انتهت مهلة طلب الشبكة، ربما بسبب انقطاع الخادم لدى موفر محطة الطقس. تحقق مرة " +"أخرى في وقت لاحق." + +#: package/contents/ui/main.qml:93 +#, kde-format +msgctxt "pressure tendency" +msgid "Rising" +msgstr "يصعد" + +#: package/contents/ui/main.qml:94 +#, kde-format +msgctxt "pressure tendency" +msgid "Falling" +msgstr "يهبط" + +#: package/contents/ui/main.qml:95 +#, kde-format +msgctxt "pressure tendency" +msgid "Steady" +msgstr "ثابت" + +#: package/contents/ui/main.qml:117 +#, kde-format +msgctxt "Wind condition" +msgid "Calm" +msgstr "هادئ" + +#: package/contents/ui/main.qml:155 +#, kde-format +msgctxt "Forecast period timeframe" +msgid "1 Day" +msgid_plural "%1 Days" +msgstr[0] "أقل من يوم" +msgstr[1] "يوم" +msgstr[2] "يومين" +msgstr[3] "%1 أيام" +msgstr[4] "%1 يومًا" +msgstr[5] "%1 يوم" + +#: package/contents/ui/main.qml:177 +#, kde-format +msgctxt "@label ground temperature" +msgid "Dewpoint:" +msgstr "نقطة النّدى:" + +#: package/contents/ui/main.qml:184 +#, kde-format +msgctxt "@label" +msgid "Pressure:" +msgstr "الضغط:" + +#: package/contents/ui/main.qml:191 +#, kde-format +msgctxt "@label pressure tendency, rising/falling/steady" +msgid "Pressure Tendency:" +msgstr "منحنى الضّغط:" + +#: package/contents/ui/main.qml:198 +#, kde-format +msgctxt "@label" +msgid "Visibility:" +msgstr "الظّهور:" + +#: package/contents/ui/main.qml:205 +#, kde-format +msgctxt "@label" +msgid "Humidity:" +msgstr "الرّطوبة:" + +#: package/contents/ui/main.qml:212 +#, kde-format +msgctxt "@label" +msgid "Wind Gust:" +msgstr "زوبعة الرياح:" + +#: package/contents/ui/main.qml:364 +#, kde-format +msgctxt "@info:tooltip %1 is the translated plasmoid name" +msgid "Click to configure %1" +msgstr "انقر لضبط %1" + +#: package/contents/ui/main.qml:375 +#, kde-format +msgctxt "weather condition + temperature" +msgid "%1 %2" +msgstr "%1 %2" + +#: package/contents/ui/main.qml:382 +#, kde-format +msgctxt "winddirection windspeed (windgust)" +msgid "%1 %2 (%3)" +msgstr "%1 %2 (%3)" + +#: package/contents/ui/main.qml:385 +#, kde-format +msgctxt "winddirection windspeed" +msgid "%1 %2" +msgstr "%1 %2" + +#: package/contents/ui/main.qml:394 +#, kde-format +msgctxt "pressure (tendency)" +msgid "%1 (%2)" +msgstr "%1 (%2)" + +#: package/contents/ui/main.qml:401 +#, kde-format +msgid "Humidity: %1" +msgstr "الرّطوبة: %1" + +#: package/contents/ui/NoticesView.qml:61 +#, kde-format +msgctxt "@action:button" +msgid "Show more information" +msgstr "أظهر المزيد من المعلومات" + +#: package/contents/ui/SwitchPanel.qml:29 +#, kde-format +msgctxt "@title:tab Weather forecast" +msgid "Forecast" +msgstr "التنبّؤ" + +#: package/contents/ui/SwitchPanel.qml:35 +#, kde-format +msgctxt "@title:tab" +msgid "Details" +msgstr "التفاصيل" + +#: package/contents/ui/SwitchPanel.qml:42 +#, kde-format +msgctxt "" +"@title:tab %1 is the number of weather notices (alerts, warnings, " +"watches, ...) issued" +msgid "%1 Notice" +msgid_plural "%1 Notices" +msgstr[0] "0 إخطار" +msgstr[1] "إخطار واحد" +msgstr[2] "إخطارين" +msgstr[3] "%1 إخطارات" +msgstr[4] "%1 إخطارا" +msgstr[5] "%1 إخطار" + +#: package/contents/ui/SwitchPanel.qml:114 +#, kde-format +msgctxt "@info:placeholder" +msgid "Unable to load weather forecast" +msgstr "غير قادر على جلب معلومات الطقس" + +#: package/contents/ui/SwitchPanel.qml:116 +#, kde-format +msgctxt "@info:usagetip" +msgid "" +"There may be a technical issue with the weather provider. If the issue " +"persists for longer than a day, submit a bug report." +msgstr "" +"قد تكون هناك مشكلة فنية مع مزود الطقس. إذا استمرت المشكلة لمدة تزيد عن يوم، " +"فأرسل تقريرًا بالأخطاء." + +#: package/contents/ui/TopPanel.qml:83 +#, kde-format +msgctxt "" +"@label %1 is the perceived temperature due to conditions like wind or " +"humidity. Use the common phrasing for this concept and keep it short, adding " +"a colon if necessary" +msgid "Feels like %1" +msgstr "الحرارة المحسوسة %1" + +#: plugin/locationlistmodel.cpp:41 +#, kde-format +msgid "Cannot find '%1' using %2." +msgstr "لا يمكن العثور على '%1' باستخدام %2." + +#: plugin/locationlistmodel.cpp:65 +#, kde-format +msgid "Connection to %1 weather server timed out." +msgstr "انتهت مهلة الاتصال بخادم الطقس %1." + +#: plugin/locationlistmodel.cpp:150 +#, kde-format +msgctxt "A weather station location and the weather service it comes from" +msgid "%1 (%2)" +msgstr "%1 (%2)" + +#: plugin/util.cpp:44 +#, kde-format +msgctxt "Degree, unit symbol" +msgid "°" +msgstr "°" + +#: plugin/util.cpp:48 plugin/util.cpp:52 +#, kde-format +msgctxt "temperature unitsymbol" +msgid "%1 %2" +msgstr "%1 %2" + +#: plugin/util.cpp:62 +#, kde-format +msgctxt "value unitsymbol" +msgid "%1 %2" +msgstr "%1 %2" + +#: plugin/util.cpp:68 +#, kde-format +msgctxt "value percentsymbol" +msgid "%1 %" +msgstr "%1 ٪" + +#: plugin/util.cpp:74 +#, kde-format +msgctxt "@item %1 is a unit description and %2 its unit symbol" +msgid "%1 (%2)" +msgstr "%1 (%2)" + +#~ msgctxt "@label" +#~ msgid "Windchill:" +#~ msgstr "برودة الرياح:" + +#~ msgctxt "@label" +#~ msgid "Humidex:" +#~ msgstr "مؤشر الرطوبة:" + +#~ msgid "Weather information retrieval for %1 timed out." +#~ msgstr "انتهت مهلة جلب معلومات الطقس لـ%1" + +#~ msgctxt "@title:column weather warnings" +#~ msgid "Warnings Issued" +#~ msgstr "صدر تحذير" + +#~ msgctxt "@title:column weather watches" +#~ msgid "Watches Issued" +#~ msgstr "صدر تنبيه" + +#~ msgctxt "@action:button" +#~ msgid "Choose…" +#~ msgstr "اختر..." + +#~ msgctxt "@title:window" +#~ msgid "Select Weather Station" +#~ msgstr "اختر محطّة الطّقس" + +#~ msgctxt "@action:button" +#~ msgid "Select" +#~ msgstr "اختر" + +#~ msgctxt "@action:button" +#~ msgid "Cancel" +#~ msgstr "ألغ" + +#~ msgctxt "@option:check Show on widget icon: temperature" +#~ msgid "Temperature" +#~ msgstr "درجة الحرارة" + +#~ msgctxt "@info:tooltip" +#~ msgid "Please configure" +#~ msgstr "رجاء اضبط" + +#~ msgctxt "weather services provider name (id)" +#~ msgid "%1 (%2)" +#~ msgstr "%1 (%2)" + +#~ msgid "Weather providers:" +#~ msgstr "مزود الطقس:" + +#~ msgctxt "@action:button" +#~ msgid "Search" +#~ msgstr "ابحث" + +#, fuzzy +#~| msgctxt "Shown when you have not set a weather provider" +#~| msgid "Please Configure" +#~ msgctxt "@action:button" +#~ msgid "Configure..." +#~ msgstr "رجاء اضبط الإعدادات" + +#, fuzzy +#~| msgid "Celsius °C" +#~ msgctxt "@item" +#~ msgid "Celsius °C" +#~ msgstr "سيليزيّ °س" + +#, fuzzy +#~| msgid "Fahrenheit °F" +#~ msgctxt "@item" +#~ msgid "Fahrenheit °F" +#~ msgstr "فهرنهايت °ف" + +#, fuzzy +#~| msgid "Hectopascals hPa" +#~ msgctxt "@item" +#~ msgid "Hectopascals hPa" +#~ msgstr "هيكتوباسكال" + +#, fuzzy +#~| msgid "Kilopascals kPa" +#~ msgctxt "@item" +#~ msgid "Kilopascals kPa" +#~ msgstr "كيلوباسكال" + +#, fuzzy +#~| msgid "Millibars mbar" +#~ msgctxt "@item" +#~ msgid "Millibars mbar" +#~ msgstr "ملّيبار" + +#, fuzzy +#~| msgid "Inches of Mercury (inHg)" +#~ msgctxt "@item" +#~ msgid "Inches of Mercury inHg" +#~ msgstr "بوصة زئبق" + +#, fuzzy +#~| msgid "Meters per Second m/s" +#~ msgctxt "@item" +#~ msgid "Meters per Second m/s" +#~ msgstr "متر في الثّانية (م/ثا)" + +#, fuzzy +#~| msgid "Kilometers per Hour km/h" +#~ msgctxt "@item" +#~ msgid "Kilometers per Hour km/h" +#~ msgstr "كيلومتر في السّاعة (كم/سا)" + +#, fuzzy +#~| msgid "Miles per Hour mph" +#~ msgctxt "@item" +#~ msgid "Miles per Hour mph" +#~ msgstr "ميل في السّاعة (ميل/سا)" + +#, fuzzy +#~| msgid "Knots kt" +#~ msgctxt "@item" +#~ msgid "Knots kt" +#~ msgstr "عقدة" + +#, fuzzy +#~| msgid "Kilometers" +#~ msgctxt "@item" +#~ msgid "Kilometers" +#~ msgstr "كيلومتر" + +#, fuzzy +#~| msgid "Miles" +#~ msgctxt "@item" +#~ msgid "Miles" +#~ msgstr "ميل" + +#, fuzzy +#~| msgctxt "content of water in air" +#~| msgid "Humidity: %1%2" +#~ msgctxt "@option:check" +#~ msgid "Show wind" +#~ msgstr "الرّطوبة: %1%2" + +#, fuzzy +#~| msgid "Pressure:" +#~ msgctxt "@option:check" +#~ msgid "Show pressure" +#~ msgstr "الضّغط:" + +#, fuzzy +#~| msgctxt "content of water in air" +#~| msgid "Humidity: %1%2" +#~ msgctxt "@option:check" +#~ msgid "Show humidity" +#~ msgstr "الرّطوبة: %1%2" + +#, fuzzy +#~| msgctxt "Short for no data available" +#~| msgid "-" +#~ msgctxt "no weather station" +#~ msgid "-" +#~ msgstr "-" + +#, fuzzy +#~| msgctxt "pressure, unit" +#~| msgid "Pressure: %1 %2" +#~ msgid "Pressure: %1" +#~ msgstr "الضّغط: %1 %2" + +#, fuzzy +#~| msgctxt "visibility from distance" +#~| msgid "Visibility: %1" +#~ msgid "Visibility: %1" +#~ msgstr "الرّؤية: %1" + +#~ msgctxt "High & Low temperature" +#~ msgid "H: %1 L: %2" +#~ msgstr "ك: %1 ص: %2" + +#~ msgctxt "Low temperature" +#~ msgid "Low: %1" +#~ msgstr "الصّغرى: %1" + +#~ msgctxt "High temperature" +#~ msgid "High: %1" +#~ msgstr "الكبرى: %1" + +#~ msgctxt "temperature, unit" +#~ msgid "%1%2" +#~ msgstr "%1%2" + +#~ msgid "N/A" +#~ msgstr "غ/م" + +#~ msgctxt "distance, unit" +#~ msgid "Visibility: %1 %2" +#~ msgstr "الرّؤية: %1 %2" + +#~ msgctxt "Percent, measure unit" +#~ msgid "%" +#~ msgstr "%" + +#~ msgctxt "Not available" +#~ msgid "N/A" +#~ msgstr "غير متوفر" + +#~ msgid "Found Places" +#~ msgstr "أماكن موجودة" + +#~ msgid "Found places" +#~ msgstr "أماكن موجودة" + +#~ msgid "Locations" +#~ msgstr "مواقع" + +#, fuzzy +#~| msgid "S&ource" +#~ msgid "S&ource:" +#~ msgstr "م&صدر" + +#, fuzzy +#~| msgid "City" +#~ msgid "City:" +#~ msgstr "المدينة" + +#, fuzzy +#~| msgid "&Wind unit" +#~ msgid "&Wind unit:" +#~ msgstr "وحدة ال&رياح" + +#, fuzzy +#~| msgid "&Visibility unit" +#~ msgid "&Visibility unit:" +#~ msgstr "وحدة ال&رؤية" + +#~ msgid "" +#~ "The applet was not able to contact the server, please try again later" +#~ msgstr "" +#~ "هذا البريمج لم يكن قادرا على الاتصال بالخادوم، فضلا حاول مجددا لاحقا." + +#~ msgid "" +#~ "The place '%1' is not valid. The data source is not able to find this " +#~ "place." +#~ msgstr "المكان '%1' غير صحيح. مصدر البيانات غير قادر على إيجاد هذا المكان." + +#~ msgid "Invalid Place" +#~ msgstr "مكان خاطئ" + +#~ msgid "&Add" +#~ msgstr "أ&ضف" + +#~ msgid "&Update Now" +#~ msgstr "ح&دث الآن" + +#~ msgctxt "wind direction, speed" +#~ msgid "%1 %2m/s" +#~ msgstr "%1 %2 م/ث" + +#~ msgctxt "wind direction, speed" +#~ msgid "%1 %2bft" +#~ msgstr "%1 %2 سلم بوفورت" + +#~ msgctxt "wind direction, speed" +#~ msgid "%1 %2mph" +#~ msgstr "%1 %2 ل/س" + +#~ msgctxt "wind direction, speed" +#~ msgid "%1 %2km/h" +#~ msgstr "%1 %2 كم/س" diff --git a/po/ar/plasma_applet_org.kde.plasma.webbrowser.po b/po/ar/plasma_applet_org.kde.plasma.webbrowser.po new file mode 100644 index 00000000..817bf3b2 --- /dev/null +++ b/po/ar/plasma_applet_org.kde.plasma.webbrowser.po @@ -0,0 +1,193 @@ +# Copyright (C) 2024 This file is copyright: +# This file is distributed under the same license as the kdeplasma-addons package. +# +# SPDX-FileCopyrightText: 2022, 2023, 2024 Zayed Al-Saidi +msgid "" +msgstr "" +"Project-Id-Version: kdeplasma-addons\n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2024-10-06 02:32+0000\n" +"PO-Revision-Date: 2024-03-30 10:53+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: ar\n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" +"X-Generator: Lokalize 23.08.5\n" + +#: package/contents/config/config.qml:12 +#, kde-format +msgctxt "@title" +msgid "General" +msgstr "عامّ" + +#: package/contents/config/config.qml:18 +#, kde-format +msgctxt "@title" +msgid "Appearance" +msgstr "المظهر" + +#: package/contents/ui/ConfigAppearance.qml:33 +#, kde-format +msgctxt "@title:group" +msgid "Icon:" +msgstr "الرمز:" + +#: package/contents/ui/ConfigAppearance.qml:34 +#, kde-format +msgctxt "@option:radio" +msgid "Website's favicon" +msgstr "رمز الموقع" + +#: package/contents/ui/ConfigAppearance.qml:57 +#, kde-format +msgctxt "@action:button" +msgid "Change Web Browser's icon" +msgstr "غير رمز متصف الويب" + +#: package/contents/ui/ConfigAppearance.qml:58 +#, kde-format +msgctxt "@info:whatsthis" +msgid "" +"Current icon is %1. Click to open menu to change the current icon or reset " +"to the default icon." +msgstr "" +"الأيقونة الحالية %1. انقر لتفتح قائمة لتغير الأيقونة الحالية أو تصفرها إلى " +"المبدئية." + +#: package/contents/ui/ConfigAppearance.qml:62 +#, kde-format +msgctxt "@info:tooltip" +msgid "Icon name is \"%1\"" +msgstr "اسم الرمز \"%1\"" + +#: package/contents/ui/ConfigAppearance.qml:97 +#, kde-format +msgctxt "@item:inmenu Open icon chooser dialog" +msgid "Choose…" +msgstr "اختر..." + +#: package/contents/ui/ConfigAppearance.qml:99 +#, kde-format +msgctxt "@info:whatsthis" +msgid "Choose an icon for Web Browser" +msgstr "اختر رمزاً لمتصفح الويب" + +#: package/contents/ui/ConfigAppearance.qml:103 +#, kde-format +msgctxt "@item:inmenu Reset icon to default" +msgid "Reset to default icon" +msgstr "صفّر إلى الرمز المبدئي" + +#: package/contents/ui/ConfigAppearance.qml:118 +#, kde-format +msgctxt "@option:check" +msgid "Display Navigation Bar" +msgstr "اعرض شريط الملاحة" + +#: package/contents/ui/ConfigGeneral.qml:21 +#, kde-format +msgctxt "@option:radio" +msgid "Load last-visited page" +msgstr "حمّل آخر صفحات زرتها" + +#: package/contents/ui/ConfigGeneral.qml:25 +#, kde-format +msgctxt "@title:group" +msgid "On startup:" +msgstr "عند البدء:" + +#: package/contents/ui/ConfigGeneral.qml:32 +#, kde-format +msgctxt "@option:radio" +msgid "Always load this page:" +msgstr "دائما حمّل هذه الصفحة:" + +#: package/contents/ui/ConfigGeneral.qml:64 package/contents/ui/main.qml:106 +#, kde-format +msgctxt "@info" +msgid "Type a URL" +msgstr "اكتب العنوان" + +#: package/contents/ui/ConfigGeneral.qml:75 +#, kde-format +msgctxt "@title:group" +msgid "Content scaling:" +msgstr "تكبير المحتويات:" + +#: package/contents/ui/ConfigGeneral.qml:79 +#, kde-format +msgctxt "@option:radio" +msgid "Fixed scale:" +msgstr "مقاس ثابت:" + +#: package/contents/ui/ConfigGeneral.qml:114 +#, kde-format +msgctxt "@option:radio" +msgid "Automatic scaling if width is below" +msgstr "لائم المقاس إذا كان العرض أقل" + +#: package/contents/ui/main.qml:67 +#, kde-format +msgctxt "@action:button" +msgid "Go Back" +msgstr "عُد" + +#: package/contents/ui/main.qml:74 +#, kde-format +msgctxt "@action:button" +msgid "Go Forward" +msgstr "تقدم" + +#: package/contents/ui/main.qml:82 +#, kde-format +msgctxt "@action:button" +msgid "Go Home" +msgstr "اذهب للصفحة الأولى" + +#: package/contents/ui/main.qml:87 +#, kde-format +msgid "Open Default URL" +msgstr "افتح العنوان المبدئي" + +#: package/contents/ui/main.qml:142 +#, kde-format +msgctxt "@action:button" +msgid "Stop Loading This Page" +msgstr "أوقف تحميل هذه الصفحة" + +#: package/contents/ui/main.qml:142 +#, kde-format +msgctxt "@action:button" +msgid "Reload This Page" +msgstr "أعد تحميل هذه الصفحة" + +#: package/contents/ui/main.qml:192 +#, kde-format +msgctxt "@action:inmenu" +msgid "Open Link in Browser" +msgstr "افتح الرابط في المتصفح" + +#: package/contents/ui/main.qml:198 +#, kde-format +msgctxt "@action:inmenu" +msgid "Copy Link Address" +msgstr "انسخ عنوان الوصلة" + +#: package/contents/ui/main.qml:260 +#, kde-format +msgctxt "An unwanted popup was blocked" +msgid "Popup blocked" +msgstr "منعت النافذة المنبثقة" + +#: package/contents/ui/main.qml:261 +#, kde-format +msgid "" +"Click here to open the following blocked popup:\n" +"%1" +msgstr "" +"انقر هنا لتفتح النوافذ المنبثقة الممنوعة التالية:\n" +"%1" diff --git a/po/ar/plasma_applet_org.kde.plasma_applet_dict.po b/po/ar/plasma_applet_org.kde.plasma_applet_dict.po new file mode 100644 index 00000000..a18220cb --- /dev/null +++ b/po/ar/plasma_applet_org.kde.plasma_applet_dict.po @@ -0,0 +1,70 @@ +# Copyright (C) YEAR This file is copyright: +# This file is distributed under the same license as the kdeplasma-addons package. +# +# Zayed Al-Saidi , 2021, 2022. +msgid "" +msgstr "" +"Project-Id-Version: kdeplasma-addons\n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2023-11-17 01:38+0000\n" +"PO-Revision-Date: 2022-06-04 21:03+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: Arabic \n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" + +#: package/contents/config/config.qml:13 +#, kde-format +msgctxt "@title" +msgid "Dictionaries" +msgstr "القواميس" + +#: package/contents/ui/AvailableDictSheet.qml:33 +#, kde-format +msgid "Add More Dictionaries" +msgstr "أضف المزيد من القواميس" + +#: package/contents/ui/ConfigDictionaries.qml:77 +#, kde-format +msgid "Unable to load dictionary list" +msgstr "لا يستطيع تحميل قائمة القواميس" + +#: package/contents/ui/ConfigDictionaries.qml:78 +#: package/contents/ui/main.qml:153 +#, kde-format +msgctxt "%2 human-readable error string" +msgid "Error code: %1 (%2)" +msgstr "رقم الخطأ: %1 (%2)" + +#: package/contents/ui/ConfigDictionaries.qml:89 +#, kde-format +msgid "No dictionaries" +msgstr "لا يوجد قواميس" + +#: package/contents/ui/ConfigDictionaries.qml:98 +#, kde-format +msgid "Add More…" +msgstr "أضف المزيد…" + +#: package/contents/ui/DictItemDelegate.qml:58 +#, kde-format +msgid "Delete" +msgstr "احذف" + +#: package/contents/ui/main.qml:44 +#, kde-format +msgctxt "@info:placeholder" +msgid "Enter word to define here…" +msgstr "ادخل كلمة ليعرفها هنا…" + +#: package/contents/ui/main.qml:152 +#, kde-format +msgid "Unable to load definition" +msgstr "غير قادر على تحميل التعريف" + +#~ msgid "Looking up definition…" +#~ msgstr "يبحث عن تعريف..." diff --git a/po/ar/plasma_calendar_alternatecalendar.po b/po/ar/plasma_calendar_alternatecalendar.po new file mode 100644 index 00000000..5853bed5 --- /dev/null +++ b/po/ar/plasma_calendar_alternatecalendar.po @@ -0,0 +1,197 @@ +# Copyright (C) YEAR This file is copyright: +# This file is distributed under the same license as the kdeplasma-addons package. +# +# Zayed Al-Saidi , 2022, 2023. +msgid "" +msgstr "" +"Project-Id-Version: kdeplasma-addons\n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2024-04-20 00:39+0000\n" +"PO-Revision-Date: 2023-05-21 09:53+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: ar\n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" + +#: calendarsystem.h:50 +#, kde-format +msgctxt "@item:inlist" +msgid "Julian" +msgstr "جوليان" + +#: calendarsystem.h:54 +#, kde-format +msgctxt "@item:inlist" +msgid "Milankovic" +msgstr "ميلانكوفيتش" + +#: calendarsystem.h:58 +#, kde-format +msgctxt "@item:inlist" +msgid "The Solar Hijri Calendar (Persian)" +msgstr "التقويم الهجري الشمسي (فارسي)" + +#: calendarsystem.h:62 +#, kde-format +msgctxt "" +"@item:inlist See https://cldr.unicode.org/development/development-process/" +"design-proposals/islamic-calendar-types for more details" +msgid "Islamic Calendar (Astronomical)" +msgstr "التقويم الهجري (فلكي)" + +#: calendarsystem.h:66 +#, kde-format +msgctxt "" +"@item:inlist See https://cldr.unicode.org/development/development-process/" +"design-proposals/islamic-calendar-types for more details" +msgid "The Islamic Civil Calendar (Tabular)" +msgstr "التقويم الهجري المدني (المجدول)" + +#: calendarsystem.h:70 +#, kde-format +msgctxt "" +"@item:inlist See https://cldr.unicode.org/development/development-process/" +"design-proposals/islamic-calendar-types for more details" +msgid "Islamic Calendar (Umm al-Qura)" +msgstr "التقويم الهجري (أم القرى)" + +#: calendarsystem.h:74 +#, kde-format +msgctxt "@item:inlist" +msgid "Chinese Lunar Calendar" +msgstr "التقويم الصيني القمري" + +#: calendarsystem.h:78 +#, kde-format +msgctxt "@item:inlist" +msgid "Indian National Calendar" +msgstr "التقويم الهندي الوطني" + +#: calendarsystem.h:82 +#, kde-format +msgctxt "@item:inlist" +msgid "Hebrew Calendar" +msgstr "التقويم العبري" + +#: config/qml/AlternateCalendarConfig.qml:39 +#, kde-format +msgctxt "@label:listbox" +msgid "Calendar system:" +msgstr "نظام التقويم:" + +#: config/qml/AlternateCalendarConfig.qml:60 +#, kde-format +msgctxt "@info:tooltip" +msgid "" +"This calendar is based on pure astronomical calculation. It doesn't consider " +"any crescent visibility criteria." +msgstr "" +"هذا التقويم يعتمد على الحساب الفلكي الصرف. ولا يعتمد على أيٍ من معايير رؤية " +"الهلال." + +#: config/qml/AlternateCalendarConfig.qml:68 +#, kde-format +msgctxt "@label:spinbox" +msgid "Date offset:" +msgstr "إزاحة التاريخ:" + +#: config/qml/AlternateCalendarConfig.qml:79 +#, kde-format +msgid "%1 day" +msgid_plural "%1 days" +msgstr[0] "لا شيء" +msgstr[1] "يوم واحد" +msgstr[2] "يومين" +msgstr[3] "%1 أيام" +msgstr[4] "%1 يومًا" +msgstr[5] "%1 يوم" + +#: config/qml/AlternateCalendarConfig.qml:82 +#, kde-format +msgctxt "@info:tooltip" +msgid "" +"A positive offset signifies a later date, while a negative offset signifies " +"an earlier date." +msgstr "" +"تشير الإزاحة الإيجابية إلى تأخير التاريخ، بينما تشير الإزاحة السلبية إلى " +"تقديم التاريخ." + +#: provider/hebrewcalendar.cpp:91 +#, kde-format +msgctxt "" +"%1 Day number %2 Translated month name in Hebrew/Jewish calendar %3 Year " +"number %4 Full date in Hebrew" +msgid "%1 %2, %3 (%4)" +msgstr "%1 %2، %3 (%4)" + +#: provider/indiancalendar.cpp:70 +#, kde-format +msgctxt "@label %1 day %2 month name in India National Calendar %3 year" +msgid "%1 %2, %3" +msgstr "%1 %2، %3" + +#: provider/islamiccalendar.cpp:130 +#, kde-format +msgctxt "" +"@label %1 Day number %2 Month name in Islamic Calendar %3 Year number %4 " +"Islamic calendar date in Arabic" +msgid "%1 %2, %3 (%4)" +msgstr "%1 %2، %3 (%4)" + +#: provider/qtcalendar.cpp:55 +#, kde-format +msgctxt "@label %1 day %2 month name %3 year" +msgid "%1 %2, %3" +msgstr "%1 %2، %3" + +#~ msgctxt "Month name in Indian National Calendar" +#~ msgid "Chaitra" +#~ msgstr "تشايترا" + +#~ msgctxt "Month name in Indian National Calendar" +#~ msgid "Vaisākha" +#~ msgstr "فايساكها" + +#~ msgctxt "Month name in Indian National Calendar" +#~ msgid "Jyēshtha" +#~ msgstr "جايستا" + +#~ msgctxt "Month name in Indian National Calendar" +#~ msgid "Āshādha" +#~ msgstr "آشادا" + +#~ msgctxt "Month name in Indian National Calendar" +#~ msgid "Shrāvana" +#~ msgstr "شرافانا" + +#~ msgctxt "Month name in Indian National Calendar" +#~ msgid "Bhādra" +#~ msgstr "بادرابادا" + +#~ msgctxt "Month name in Indian National Calendar" +#~ msgid "Āshwin" +#~ msgstr "أسويجا" + +#~ msgctxt "Month name in Indian National Calendar" +#~ msgid "Kārtika" +#~ msgstr "كارتيكا" + +#~ msgctxt "Month name in Indian National Calendar" +#~ msgid "Mārgaśīrṣa" +#~ msgstr "مارغاسيرا" + +#~ msgctxt "Month name in Indian National Calendar" +#~ msgid "Pausha" +#~ msgstr "بوشيا" + +#~ msgctxt "Month name in Indian National Calendar" +#~ msgid "Māgha" +#~ msgstr "ماغها" + +#~ msgctxt "Month name in Indian National Calendar" +#~ msgid "Phālguna" +#~ msgstr "فالغونا" diff --git a/po/ar/plasma_calendar_astronomicalevents.po b/po/ar/plasma_calendar_astronomicalevents.po new file mode 100644 index 00000000..cd04a3ee --- /dev/null +++ b/po/ar/plasma_calendar_astronomicalevents.po @@ -0,0 +1,35 @@ +# Copyright (C) YEAR This file is copyright: +# This file is distributed under the same license as the kdeplasma-addons package. +# +# Zayed Al-Saidi , 2021. +msgid "" +msgstr "" +"Project-Id-Version: kdeplasma-addons\n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2020-09-25 02:25+0200\n" +"PO-Revision-Date: 2021-07-05 19:02+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: ar\n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" + +#: config/qml/AstronomicalEventsConfig.qml:39 +#, kde-format +msgid "Show:" +msgstr "أظهر:" + +#: config/qml/AstronomicalEventsConfig.qml:42 +#, kde-format +msgctxt "@option:check" +msgid "Lunar phases" +msgstr "مراحل القمر" + +#: config/qml/AstronomicalEventsConfig.qml:50 +#, kde-format +msgctxt "@option:check" +msgid "Astronomical seasons (solstices and equinoxes)" +msgstr "المواسم الفلكية (الانقلابات والاعتدالات)" diff --git a/po/ar/plasma_runner_CharacterRunner.po b/po/ar/plasma_runner_CharacterRunner.po new file mode 100644 index 00000000..532439fb --- /dev/null +++ b/po/ar/plasma_runner_CharacterRunner.po @@ -0,0 +1,83 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Safa Alfulaij , 2016. +# Zayed Al-Saidi , 2021. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2023-11-04 01:38+0000\n" +"PO-Revision-Date: 2021-07-04 22:10+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: ar\n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" + +#: charrunner.cpp:40 +#, kde-format +msgid "" +"Creates Characters from :q: if it is a hexadecimal code or defined alias." +msgstr "يُنشئ المحارف من :q: إن كان رمزًا عشريًّا أو اختصارًا مُعرّفًا." + +#: charrunner_config.cpp:60 +#, kde-format +msgctxt "Message that config is corrupted" +msgid "" +"Config entries for alias list and code list have different sizes, ignoring " +"all." +msgstr "" +"إدخالات التكوين لقائمة الأسماء المستعارة وقائمة الرموز لها أحجام مختلفة ، " +"تجاهلالكل. " + +#. i18n: ectx: property (title), widget (QGroupBox, main) +#: charrunner_config.ui:18 +#, kde-format +msgid "Character Runner Config" +msgstr "ضبط مشغّل المحارف" + +#. i18n: ectx: property (text), widget (QLabel, label_trigger) +#: charrunner_config.ui:26 +#, kde-format +msgid "&Trigger word:" +msgstr "كلمة التّح&فيز:" + +#. i18n: ectx: property (text), widget (QLabel, label_alias) +#: charrunner_config.ui:47 +#, kde-format +msgid "Alias:" +msgstr "الاختصار:" + +#. i18n: ectx: property (text), widget (QLabel, label_hex) +#: charrunner_config.ui:61 +#, kde-format +msgid "Hex code:" +msgstr "الرمز السادس عشريّ:" + +#. i18n: ectx: property (text), widget (QTreeWidget, list) +#: charrunner_config.ui:89 +#, kde-format +msgid "Alias" +msgstr "الاختصار" + +#. i18n: ectx: property (text), widget (QTreeWidget, list) +#: charrunner_config.ui:94 +#, kde-format +msgid "Code" +msgstr "الكود" + +#. i18n: ectx: property (text), widget (QPushButton, addItem) +#: charrunner_config.ui:104 +#, kde-format +msgid "Add Item" +msgstr "أضف عنصرًا" + +#. i18n: ectx: property (text), widget (QPushButton, deleteItem) +#: charrunner_config.ui:114 +#, kde-format +msgid "Delete Item" +msgstr "احذف العنصر" diff --git a/po/ar/plasma_runner_converterrunner.po b/po/ar/plasma_runner_converterrunner.po new file mode 100644 index 00000000..9b7cc926 --- /dev/null +++ b/po/ar/plasma_runner_converterrunner.po @@ -0,0 +1,602 @@ +# translation of krunner_converterrunner.po to Arabic +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# zayed , 2008, 2021, 2023. +# Safa Alfulaij , 2015. +msgid "" +msgstr "" +"Project-Id-Version: krunner_converterrunner\n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2024-09-29 02:29+0000\n" +"PO-Revision-Date: 2023-05-04 10:43+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: ar\n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" + +#: converterrunner.cpp:30 +#, kde-format +msgid "Copy unit and number" +msgstr "انسخ الوحدة والرقم" + +#: converterrunner.cpp:33 +#, kde-format +msgid "" +"Converts the value of :q: when :q: is made up of value unit [>, to, as, in] " +"unit.You can use the Unit converter applet to find all available units." +msgstr "" +"يحوّل قيمة :q: عندما تكون قيمة :q: هي القيمة الوحدة [>، إلى] الوحدة. يمكنك " +"استخدام بريمج محوّل الوحدات للعثور على كلّ الوحدات المتوفّرة." + +#: converterrunner.cpp:40 +#, kde-format +msgctxt "list of words that can used as amount of 'unit1' [in|to|as] 'unit2'" +msgid "in;to;as" +msgstr "إلى" + +#, fuzzy +#~| msgid "in" +#~ msgctxt "amount of 'unit1' in 'unit2'" +#~ msgid "in" +#~ msgstr "بوصة" + +#, fuzzy +#~| msgid "ton" +#~ msgctxt "amount of 'unit1' to 'unit2'" +#~ msgid "to" +#~ msgstr "طن" + +#, fuzzy +#~| msgctxt "amount of as " +#~| msgid "as" +#~ msgctxt "amount of 'unit1' as 'unit2'" +#~ msgid "as" +#~ msgstr "إلى" + +#~ msgctxt "amount of in " +#~ msgid "in" +#~ msgstr "إلى" + +#~ msgid "square meter" +#~ msgstr "متر مربع" + +#~ msgid "square meters" +#~ msgstr "أمتار مربعة" + +#~ msgid "square kilometer" +#~ msgstr "كيلومتر مربع" + +#~ msgid "square kilometers" +#~ msgstr "كليلومترات مربعة" + +#~ msgid "square centimeter" +#~ msgstr "سنتيمتر مربع" + +#~ msgid "square centimeters" +#~ msgstr "سنتيمترات مربعة" + +#~ msgid "square millimeter" +#~ msgstr "ملليمتر مربع" + +#~ msgid "square millimeters" +#~ msgstr "ملليمترات مربعة" + +#~ msgid "hectares" +#~ msgstr "هكتارات" + +#~ msgid "hectare" +#~ msgstr "هكتار" + +#~ msgid "hectometer" +#~ msgstr "هكتومتر" + +#~ msgid "hectometers" +#~ msgstr "هكتومترات" + +#~ msgid "acre" +#~ msgstr "أكر" + +#~ msgid "square foot" +#~ msgstr "قدم مربع" + +#~ msgid "square feet" +#~ msgstr "أقدام مربعة" + +#~ msgid "square ft" +#~ msgstr "قدم مربع" + +#~ msgid "sq foot" +#~ msgstr "قدم مربع" + +#~ msgid "sq ft" +#~ msgstr "قدم مربع" + +#~ msgid "sq feet" +#~ msgstr "أقدام مربعة" + +#~ msgid "feet²" +#~ msgstr "قدم²" + +#~ msgid "ft²" +#~ msgstr "قدم²" + +#~ msgid "square inch" +#~ msgstr "بوصة مربعة" + +#~ msgid "square inches" +#~ msgstr "بوصات مربعة" + +#~ msgid "square in" +#~ msgstr "بوصة مربعة" + +#~ msgid "sq inches" +#~ msgstr "بوصة مربعة" + +#~ msgid "sq inch" +#~ msgstr "بوصة مربعة" + +#~ msgid "sq in" +#~ msgstr "بوصة مربعة" + +#~ msgid "inch²" +#~ msgstr "بوصة²" + +#~ msgid "in²" +#~ msgstr "بوصة²" + +#~ msgid "square mile" +#~ msgstr "ميل مربع" + +#~ msgid "square mi" +#~ msgstr "ميل مربع" + +#~ msgid "sq miles" +#~ msgstr "أميال مربع" + +#~ msgid "sq mile" +#~ msgstr "ميل مبريع" + +#~ msgid "sq mi" +#~ msgstr "ميل مربع" + +#~ msgid "mile²" +#~ msgstr "ميل²" + +#~ msgid "mi²" +#~ msgstr "ميل²" + +#~ msgid "square miles" +#~ msgstr "أميال مربعة" + +#~ msgid "Area" +#~ msgstr "مساحة" + +#~ msgid "meter" +#~ msgstr "متر" + +#~ msgid "meters" +#~ msgstr "أمتار" + +#~ msgid "kilometer" +#~ msgstr "كيلومتر" + +#~ msgid "kilometers" +#~ msgstr "كيلومترات" + +#~ msgid "decimeter" +#~ msgstr "ديسيمتر" + +#~ msgid "decimeters" +#~ msgstr "ديسميترات" + +#~ msgid "centimeter" +#~ msgstr "سنتيمتر" + +#~ msgid "centimeters" +#~ msgstr "سنتيمترات" + +#~ msgid "millimeter" +#~ msgstr "ميلليمتر" + +#~ msgid "millimeters" +#~ msgstr "ميلليمترات" + +#~ msgid "micrometer" +#~ msgstr "ميكرومتر" + +#~ msgid "micrometers" +#~ msgstr "ميكرومترات" + +#~ msgid "nanometer" +#~ msgstr "نانومتر" + +#~ msgid "nanometers" +#~ msgstr "نانومترات" + +#~ msgid "picometer" +#~ msgstr "بيكومتر" + +#~ msgid "picometers" +#~ msgstr "بيكومترات" + +#~ msgid "femtometer" +#~ msgstr "فيمتومتر" + +#~ msgid "inch" +#~ msgstr "بوصة" + +#~ msgid "inches" +#~ msgstr "بوصات" + +#~ msgid "\"" +#~ msgstr "\"" + +#~ msgid "foot" +#~ msgstr "قدم" + +#~ msgid "feet" +#~ msgstr "أقدام" + +#~ msgid "ft" +#~ msgstr "قدم" + +#~ msgid "yard" +#~ msgstr "ياردة" + +#~ msgid "yards" +#~ msgstr "ياردات" + +#~ msgid "yd" +#~ msgstr "يادرة" + +#~ msgid "mile" +#~ msgstr "كيل" + +#~ msgid "miles" +#~ msgstr "أميال" + +#~ msgid "ml" +#~ msgstr "ميل" + +#~ msgid "nautical mile" +#~ msgstr "ميل بحري" + +#~ msgid "nautical miles" +#~ msgstr "أميال بحرية" + +#~ msgid "nmi" +#~ msgstr "ميل بحري" + +#~ msgid "ly" +#~ msgstr "سنة ضوئية" + +#~ msgid "light-year" +#~ msgstr "سنة ضوئية" + +#~ msgid "light-years" +#~ msgstr "سنوات ضوئية" + +#~ msgid "lightyear" +#~ msgstr "سنة ضوئية" + +#~ msgid "lightyears" +#~ msgstr "سنوات ضوئية" + +#~ msgid "pc" +#~ msgstr "فرسخ" + +#~ msgid "parsec" +#~ msgstr "فرسخ" + +#~ msgid "parsecs" +#~ msgstr "فراسخ" + +#~ msgid "au" +#~ msgstr "وحدة فلكية" + +#~ msgid "astronomical unit" +#~ msgstr "وحدة فلكية" + +#~ msgid "astronomical units" +#~ msgstr "وحدات فلكية" + +#~ msgid "Length" +#~ msgstr "طول" + +#~ msgid "gram" +#~ msgstr "جرام" + +#~ msgid "grams" +#~ msgstr "جرامات" + +#~ msgid "kilogram" +#~ msgstr "كيلوجرام" + +#~ msgid "kilograms" +#~ msgstr "كيلوجرامات" + +#~ msgid "decigram" +#~ msgstr "ديسيجرام" + +#~ msgid "decigrams" +#~ msgstr "ديسيجرامات" + +#~ msgid "centigram" +#~ msgstr "سنتيجرام" + +#~ msgid "centigrams" +#~ msgstr "سنتيجرامات" + +#~ msgid "milligram" +#~ msgstr "ميلليجرام" + +#~ msgid "milligrams" +#~ msgstr "ميلليجرامات" + +#~ msgid "microgram" +#~ msgstr "ميكروجرام" + +#~ msgid "micrograms" +#~ msgstr "ميكروجرامات" + +#~ msgid "tons" +#~ msgstr "أطنان" + +#~ msgid "tonne" +#~ msgstr "طن" + +#~ msgid "carat" +#~ msgstr "قيراط" + +#~ msgid "carats" +#~ msgstr "قراريط" + +#~ msgid "pound" +#~ msgstr "رطل" + +#~ msgid "pounds" +#~ msgstr "أرطال" + +#~ msgid "ounce" +#~ msgstr "أونصة" + +#~ msgid "ounces" +#~ msgstr "أونصات" + +#~ msgid "troy ounce" +#~ msgstr "أوقية" + +#~ msgid "troy ounces" +#~ msgstr "أوقيات" + +#~ msgid "newton" +#~ msgstr "نيوتن" + +#~ msgid "kilonewton" +#~ msgstr "كيلونيوتن" + +#~ msgid "Mass" +#~ msgstr "كتلة" + +#~ msgid "meters per second" +#~ msgstr "متر في الثانية" + +#~ msgid "kilometers per hour" +#~ msgstr "كيلومتر في الساعة" + +#~ msgid "miles per hour" +#~ msgstr "ميل في الساعة" + +#~ msgid "mph" +#~ msgstr "ميل في الساعة" + +#~ msgid "foot per second" +#~ msgstr "قدم في الثانية" + +#~ msgid "feet per second" +#~ msgstr "قدم في الثانية" + +#~ msgid "ft/s" +#~ msgstr "قدم/ث" + +#~ msgid "inch per second" +#~ msgstr "بوصة في الثانية" + +#~ msgid "in/s" +#~ msgstr "بوصة/ث" + +#~ msgid "knot" +#~ msgstr "عقدة" + +#~ msgid "knots" +#~ msgstr "عقد" + +#~ msgid "nautical miles per hour" +#~ msgstr "أميال بحرية في الساعة" + +#~ msgid "speed of sound" +#~ msgstr "سرعة الصوت" + +#~ msgid "machs" +#~ msgstr "ماخ" + +#~ msgid "mach" +#~ msgstr "ماخ" + +#~ msgid "speed of light" +#~ msgstr "سرعة الضوء" + +#~ msgid "Speed" +#~ msgstr "سرعة" + +#~ msgid "Temperature" +#~ msgstr "درجة الحرارة" + +#~ msgid "cubic meter" +#~ msgstr "متر مكعب" + +#~ msgid "cubic meters" +#~ msgstr "أمتار مكعبة" + +#~ msgid "cubic kilometer" +#~ msgstr "كيلومتر مكعب" + +#~ msgid "cubic kilometers" +#~ msgstr "كيلومترات مكعبة" + +#~ msgid "cubic centimeter" +#~ msgstr "سنتيمتر مكعب" + +#~ msgid "cubic centimeters" +#~ msgstr "سنتيمترات مكعبة" + +#~ msgid "cubic millimeter" +#~ msgstr "ميلليمتر مكعب" + +#~ msgid "cubic millimeters" +#~ msgstr "ميلليمترات مكعبة" + +#~ msgid "liter" +#~ msgstr "لتر" + +#~ msgid "liters" +#~ msgstr "لترات" + +#~ msgid "centiliter" +#~ msgstr "سنتيلتر" + +#~ msgid "centiliters" +#~ msgstr "سنتيلترات" + +#~ msgid "milliliter" +#~ msgstr "ميلليلتر" + +#~ msgid "milliliters" +#~ msgstr "ميلليلترات" + +#~ msgid "cubic foot" +#~ msgstr "قدم مكعب" + +#~ msgid "cubic feet" +#~ msgstr "أقدام مكعبة" + +#~ msgid "cubic ft" +#~ msgstr "قدم مكعب" + +#~ msgid "cu foot" +#~ msgstr "قدم مكعب" + +#~ msgid "cu ft" +#~ msgstr "قدم مكعب" + +#~ msgid "cu feet" +#~ msgstr "أقدام مكعبة" + +#~ msgid "feet³" +#~ msgstr "أقدام³" + +#~ msgid "ft³" +#~ msgstr "قدم³" + +#~ msgid "cubic inch" +#~ msgstr "بوصة مكعبة" + +#~ msgid "cubic inches" +#~ msgstr "بوصات مكعبة" + +#~ msgid "cubic in" +#~ msgstr "بوصة مكعبة" + +#~ msgid "cu inches" +#~ msgstr "بوصات مكعبة" + +#~ msgid "cu inch" +#~ msgstr "بوصة مكعبة" + +#~ msgid "cu in" +#~ msgstr "بوصة مكعبة" + +#~ msgid "inch³" +#~ msgstr "بوصة³" + +#~ msgid "in³" +#~ msgstr "بوصة³" + +#~ msgid "cubic mile" +#~ msgstr "ميل مكعب" + +#~ msgid "cubic mi" +#~ msgstr "ميل مكعب" + +#~ msgid "cu miles" +#~ msgstr "أميال مكعبة" + +#~ msgid "cu mile" +#~ msgstr "ميل مكعب" + +#~ msgid "cu mi" +#~ msgstr "ميل مكعب" + +#~ msgid "mile³" +#~ msgstr "ميل³" + +#~ msgid "mi³" +#~ msgstr "ميل³" + +#~ msgid "cubic miles" +#~ msgstr "أميال مكعبة" + +#~ msgid "oz.fl." +#~ msgstr "أوقية سائلة" + +#~ msgid "fluid ounces" +#~ msgstr "أوقيات سائلة" + +#~ msgid "oz. fl." +#~ msgstr "أوقية سائلة" + +#~ msgid "fl.oz." +#~ msgstr "أوقية سائلة" + +#~ msgid "fl. oz." +#~ msgstr "أوقية سائلة" + +#~ msgid "fl oz" +#~ msgstr "أوقية سائلة" + +#~ msgid "fluid ounce" +#~ msgstr "أوقية سائلة" + +#~ msgid "cp" +#~ msgstr "كوب" + +#~ msgid "cups" +#~ msgstr "أكواب" + +#~ msgid "cup" +#~ msgstr "كوب" + +#~ msgid "gal" +#~ msgstr "جالون" + +#~ msgid "gallons (U.S. liquid)" +#~ msgstr "جالونات ( أمريكية)" + +#~ msgid "gallon" +#~ msgstr "جالون" + +#~ msgid "gallons" +#~ msgstr "جالونات" + +#~ msgid "pint" +#~ msgstr "بنت" + +#~ msgid "pints (imperial)" +#~ msgstr "بنتات (امبريالي)" + +#~ msgid "Volume" +#~ msgstr "حجم" diff --git a/po/ar/plasma_runner_datetime.po b/po/ar/plasma_runner_datetime.po new file mode 100644 index 00000000..7f8cda96 --- /dev/null +++ b/po/ar/plasma_runner_datetime.po @@ -0,0 +1,167 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Safa Alfulaij , 2015. +# Zayed Al-Saidi , 2022, 2023. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2023-11-04 01:38+0000\n" +"PO-Revision-Date: 2023-02-16 12:26+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: ar\n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" + +#: datetimerunner.cpp:23 +#, kde-format +msgctxt "Note this is a KRunner keyword" +msgid "date" +msgstr "التاريخ" + +#: datetimerunner.cpp:24 +#, kde-format +msgctxt "Note this is a KRunner keyword" +msgid "time" +msgstr "الوقت" + +#: datetimerunner.cpp:28 +#, kde-format +msgctxt "" +"words to specify a time in a time zone or to convert a time to a time zone, " +"e.g. 'current time in Berlin' or '18:00 UTC to CET', separated by '|' (will " +"be used as a regex)" +msgid "to|in|as|at" +msgstr "إلى|في" + +#: datetimerunner.cpp:34 +#, kde-format +msgid "Displays the current date" +msgstr "يعرض التّاريخ الحاليّ" + +#: datetimerunner.cpp:35 +#, kde-format +msgid "Displays the current time" +msgstr "يعرض الوقت الحاليّ" + +#: datetimerunner.cpp:36 datetimerunner.cpp:38 +#, kde-format +msgctxt "The <> and space are part of the example query" +msgid " " +msgstr " <المنطقة الزمنية>" + +#: datetimerunner.cpp:37 +#, kde-format +msgid "" +"Displays the current date and difference to system date in a given timezone" +msgstr "يعرض التّاريخ الحاليّ والفرق بين تاريخ النظام والمنطقة الزّمنية المعطاة" + +#: datetimerunner.cpp:39 +#, kde-format +msgid "" +"Displays the current time and difference to system time in a given timezone" +msgstr "يعرض الوقت الحاليّ والفرق بين وقت النظام والمنطقة الزّمنية المعطاة" + +#: datetimerunner.cpp:40 +#, kde-format +msgctxt "The <> and space are part of the example query" +msgid "

+ * + * Image Credit & + *
Copyright: + * + * Elena Pinna + */ + const QRegularExpression infoRegEx(QStringLiteral("
.*?(.+?).*?Credit.*?(.+?)
")); + const QRegularExpressionMatch match = infoRegEx.match(data); + + if (match.hasMatch()) { + m_title = QTextDocumentFragment::fromHtml(match.captured(1).trimmed()).toPlainText(); + m_author = QTextDocumentFragment::fromHtml(match.captured(2).trimmed()).toPlainText(); + } + + KIO::StoredTransferJob *imageJob = KIO::storedGet(m_remoteUrl, KIO::NoReload, KIO::HideProgressInfo); + connect(imageJob, &KIO::StoredTransferJob::finished, this, &ApodProvider::imageRequestFinished); + } else { + Q_EMIT error(this); + } +} + +void ApodProvider::imageRequestFinished(KJob *_job) +{ + KIO::StoredTransferJob *job = static_cast(_job); + if (job->error()) { + Q_EMIT error(this); + return; + } + + Q_EMIT finished(this, QImage::fromData(job->data())); +} + +K_PLUGIN_CLASS_WITH_JSON(ApodProvider, "apodprovider.json") + +#include "apodprovider.moc" diff --git a/wallpapers/potd/plugins/providers/apodprovider.h b/wallpapers/potd/plugins/providers/apodprovider.h new file mode 100644 index 00000000..518b6563 --- /dev/null +++ b/wallpapers/potd/plugins/providers/apodprovider.h @@ -0,0 +1,33 @@ +/* + * SPDX-FileCopyrightText: 2007 Tobias Koenig + * SPDX-FileCopyrightText: 2008 Anne-Marie Mahfouf + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#pragma once + +#include "potdprovider.h" + +class KJob; + +/** + * This class provides the image for APOD + * "Astronomy Picture Of the Day" + * located at https://antwrp.gsfc.nasa.gov/apod. + * Direct link to the picture of the day page is + * https://antwrp.gsfc.nasa.gov/apod/apYYMMDD.html + * where YY is the year last 2 digits, + * MM is the month and DD the day, in 2 digits. + */ +class ApodProvider : public PotdProvider +{ + Q_OBJECT + +public: + explicit ApodProvider(QObject *parent, const KPluginMetaData &data, const QVariantList &args); + +private: + void pageRequestFinished(KJob *job); + void imageRequestFinished(KJob *job); +}; diff --git a/wallpapers/potd/plugins/providers/apodprovider.json b/wallpapers/potd/plugins/providers/apodprovider.json new file mode 100644 index 00000000..57df6494 --- /dev/null +++ b/wallpapers/potd/plugins/providers/apodprovider.json @@ -0,0 +1,51 @@ +{ + "KPlugin": { + "Icon": "", + "Name": "Astronomy (NASA)", + "Name[ar]": "الفلك (ناسا)", + "Name[ast]": "Astronomía (NASA)", + "Name[az]": "Astronomiya (NASA)", + "Name[bg]": "Астрономия (NASA)", + "Name[ca@valencia]": "Astronomia (NASA)", + "Name[ca]": "Astronomia (NASA)", + "Name[cs]": "Astronomie (NASA)", + "Name[da]": "Astronomi (NASA)", + "Name[de]": "Astronomie (NASA)", + "Name[en_GB]": "Astronomy (NASA)", + "Name[eo]": "Astronomio (NASA)", + "Name[es]": "Astronomía (NASA)", + "Name[et]": "Astronomy (NASA)", + "Name[eu]": "Astronomia (NASA)", + "Name[fi]": "Tähtitiede (Nasa)", + "Name[fr]": "Astronomie (NASA)", + "Name[gl]": "Astronomía (NASA)", + "Name[he]": "אסטרונומיה (NASA)", + "Name[hu]": "Csillagászat (NASA)", + "Name[ia]": "Astronomy (NASA)", + "Name[id]": "Astronomy (NASA)", + "Name[is]": "Stjörnufræði (NASA)", + "Name[it]": "Astronomia (NASA)", + "Name[ja]": "天文学 (NASA)", + "Name[ka]": "ასტრონომია (NASA)", + "Name[ko]": "천문(NASA)", + "Name[lt]": "Astronomija (NASA)", + "Name[lv]": "Astronomija (NASA)", + "Name[nl]": "Astronomie (NASA)", + "Name[nn]": "Astronomi (NASA)", + "Name[pl]": "Astronomia (NASA)", + "Name[pt]": "Astronomy (NASA)", + "Name[pt_BR]": "Astronomia (NASA)", + "Name[ro]": "Astronomie (NASA)", + "Name[ru]": "Астрономия (NASA)", + "Name[sk]": "Astronómia (NASA)", + "Name[sl]": "Astronomy (NASA)", + "Name[sv]": "Astronomi (NASA)", + "Name[tr]": "Gökbilim (NASA)", + "Name[uk]": "Астрономія (NASA)", + "Name[vi]": "Thiên văn học (NASA)", + "Name[x-test]": "xxAstronomy (NASA)xx", + "Name[zh_CN]": "天文 (NASA)", + "Name[zh_TW]": "Astronomy (NASA)" + }, + "X-KDE-PlasmaPoTDProvider-Identifier": "apod" +} diff --git a/wallpapers/potd/plugins/providers/bingprovider.cpp b/wallpapers/potd/plugins/providers/bingprovider.cpp new file mode 100644 index 00000000..3fb944ae --- /dev/null +++ b/wallpapers/potd/plugins/providers/bingprovider.cpp @@ -0,0 +1,107 @@ +/* + * SPDX-FileCopyrightText: 2017 Weng Xuetian + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "bingprovider.h" + +#include +#include +#include // Extract from the copyright text + +#include +#include + +BingProvider::BingProvider(QObject *parent, const KPluginMetaData &data, const QVariantList &args) + : PotdProvider(parent, data, args) + , m_screenWidth(args.size() >= 2 ? args[0].toInt() : 0) + , m_screenHeight(args.size() >= 2 ? args[1].toInt() : 0) +{ + const QUrl url(QStringLiteral("https://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1")); + + KIO::StoredTransferJob *job = KIO::storedGet(url, KIO::NoReload, KIO::HideProgressInfo); + connect(job, &KIO::StoredTransferJob::finished, this, &BingProvider::pageRequestFinished); +} + +void BingProvider::pageRequestFinished(KJob *_job) +{ + KIO::StoredTransferJob *job = static_cast(_job); + if (job->error()) { + Q_EMIT error(this); + return; + } + + auto json = QJsonDocument::fromJson(job->data()); + do { + if (json.isNull()) { + break; + } + auto imagesArray = json.object().value(QLatin1String("images")); + if (!imagesArray.isArray() || imagesArray.toArray().size() <= 0) { + break; + } + auto imageObj = imagesArray.toArray().at(0); + if (!imageObj.isObject()) { + break; + } + const QJsonObject imageObject = imageObj.toObject(); + auto url = imageObject.value(QLatin1String("urlbase")); + QString urlString = url.isString() ? url.toString() : QString(); + if (urlString.isEmpty()) { + break; + } + + urlString = QStringLiteral("https://www.bing.com/") + urlString; + + if (m_screenWidth > 1920 || m_screenHeight > 1080) { + // Use 4k wallpaper + urlString += QStringLiteral("_UHD.jpg"); + } else { + urlString += QStringLiteral("_1920x1080.jpg"); + } + m_remoteUrl = QUrl(urlString); + + // Parse the title and the copyright text from the json data + // Example copyright text: "草丛中的母狮和它的幼崽,南非 (© Andrew Coleman/Getty Images)" + const QString copyright = imageObject.value(QStringLiteral("copyright")).toString(); + const QRegularExpression copyrightRegEx(QStringLiteral("(.+?)[\\((](.+?)[\\))]")); + if (const QRegularExpressionMatch match = copyrightRegEx.match(copyright); match.hasMatch()) { + // In some regions "title" is empty, so extract the title from the copyright text. + m_title = match.captured(1).trimmed(); + m_author = match.captured(2).remove(QStringLiteral("©")).trimmed(); + } + + const QString title = imageObject.value(QStringLiteral("title")).toString(); + if (!title.isEmpty()) { + m_title = title; + } + + const QString infoUrl = imageObject.value(QStringLiteral("copyrightlink")).toString(); + if (!infoUrl.isEmpty()) { + m_infoUrl = QUrl(infoUrl); + } + + KIO::StoredTransferJob *imageJob = KIO::storedGet(m_remoteUrl, KIO::NoReload, KIO::HideProgressInfo); + connect(imageJob, &KIO::StoredTransferJob::finished, this, &BingProvider::imageRequestFinished); + return; + } while (0); + + Q_EMIT error(this); + return; +} + +void BingProvider::imageRequestFinished(KJob *_job) +{ + KIO::StoredTransferJob *job = static_cast(_job); + if (job->error()) { + Q_EMIT error(this); + return; + } + QByteArray data = job->data(); + Q_EMIT finished(this, QImage::fromData(data)); +} + +K_PLUGIN_CLASS_WITH_JSON(BingProvider, "bingprovider.json") + +#include "bingprovider.moc" diff --git a/wallpapers/potd/plugins/providers/bingprovider.h b/wallpapers/potd/plugins/providers/bingprovider.h new file mode 100644 index 00000000..a25473e3 --- /dev/null +++ b/wallpapers/potd/plugins/providers/bingprovider.h @@ -0,0 +1,30 @@ +/* + * SPDX-FileCopyrightText: 2017 Weng Xuetian + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#pragma once + +#include "potdprovider.h" + +class KJob; + +/** + * This class provides the image for the Bing's homepage + * url is obtained from https://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1 + */ +class BingProvider : public PotdProvider +{ + Q_OBJECT + +public: + explicit BingProvider(QObject *parent, const KPluginMetaData &data, const QVariantList &args); + +private: + void pageRequestFinished(KJob *job); + void imageRequestFinished(KJob *job); + + int m_screenWidth; + int m_screenHeight; +}; diff --git a/wallpapers/potd/plugins/providers/bingprovider.json b/wallpapers/potd/plugins/providers/bingprovider.json new file mode 100644 index 00000000..3332895c --- /dev/null +++ b/wallpapers/potd/plugins/providers/bingprovider.json @@ -0,0 +1,51 @@ +{ + "KPlugin": { + "Icon": "", + "Name": "Bing", + "Name[ar]": "محرك بنج", + "Name[ast]": "Bing", + "Name[az]": "Bing", + "Name[bg]": "Bing", + "Name[ca@valencia]": "Bing", + "Name[ca]": "Bing", + "Name[cs]": "Bing", + "Name[da]": "Bing", + "Name[de]": "Bing", + "Name[en_GB]": "Bing", + "Name[eo]": "Bing", + "Name[es]": "Bing", + "Name[et]": "Bing", + "Name[eu]": "Bing", + "Name[fi]": "Bing", + "Name[fr]": "Bing", + "Name[gl]": "Bing", + "Name[he]": "בינג", + "Name[hu]": "Bing", + "Name[ia]": "Bing", + "Name[id]": "Bing", + "Name[is]": "Bing", + "Name[it]": "Bing", + "Name[ja]": "Bing", + "Name[ka]": "Bing", + "Name[ko]": "Bing", + "Name[lt]": "Bing", + "Name[lv]": "„Bing“", + "Name[nl]": "Bing", + "Name[nn]": "Bing", + "Name[pl]": "Bing", + "Name[pt]": "Bing", + "Name[pt_BR]": "Bing", + "Name[ro]": "Bing", + "Name[ru]": "Bing", + "Name[sk]": "Bing", + "Name[sl]": "Bing", + "Name[sv]": "Bing", + "Name[tr]": "Bing", + "Name[uk]": "Bing", + "Name[vi]": "Bing", + "Name[x-test]": "xxBingxx", + "Name[zh_CN]": "必应", + "Name[zh_TW]": "Bing" + }, + "X-KDE-PlasmaPoTDProvider-Identifier": "bing" +} diff --git a/wallpapers/potd/plugins/providers/epodprovider.cpp b/wallpapers/potd/plugins/providers/epodprovider.cpp new file mode 100644 index 00000000..f48e2c13 --- /dev/null +++ b/wallpapers/potd/plugins/providers/epodprovider.cpp @@ -0,0 +1,91 @@ +/* + * SPDX-FileCopyrightText: 2007 Tobias Koenig + * SPDX-FileCopyrightText: 2008 Anne-Marie Mahfouf + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "epodprovider.h" + +#include +#include + +#include +#include + +EpodProvider::EpodProvider(QObject *parent, const KPluginMetaData &data, const QVariantList &args) + : PotdProvider(parent, data, args) +{ + const QUrl url(QStringLiteral("https://epod.usra.edu/blog/")); + + KIO::StoredTransferJob *job = KIO::storedGet(url, KIO::NoReload, KIO::HideProgressInfo); + connect(job, &KIO::StoredTransferJob::finished, this, &EpodProvider::pageRequestFinished); +} + +void EpodProvider::pageRequestFinished(KJob *_job) +{ + KIO::StoredTransferJob *job = static_cast(_job); + if (job->error()) { + Q_EMIT error(this); + return; + } + + const QString data = QString::fromUtf8(job->data()).simplified(); + + const QString pattern = QStringLiteral("://epod.usra.edu/.a/*-pi"); + const auto exp = QRegularExpression(QRegularExpression::wildcardToRegularExpression(pattern)); + const auto expMatch = exp.match(data); + + if (expMatch.hasMatch()) { + /** + * Match link and title + * Example: + *

+ * Archive - Panamint Delta + *

+ */ + const QRegularExpression titleRegEx(QStringLiteral("

.*?(.+?).*?

")); + const QRegularExpressionMatch titleMatch = titleRegEx.match(data); + if (titleMatch.hasMatch()) { + m_infoUrl = QUrl(titleMatch.captured(1).trimmed()); + m_title = QTextDocumentFragment::fromHtml(titleMatch.captured(2).trimmed()).toPlainText(); + } + + /** + * Match author + * Example: + * Photographer: Wendy Van Norden + */ + const QRegularExpression authorRegEx(QStringLiteral("Photographer.*?.*?(.+?)")); + const QRegularExpressionMatch authorMatch = authorRegEx.match(data); + if (authorMatch.hasMatch()) { + m_author = QTextDocumentFragment::fromHtml(authorMatch.captured(1).trimmed()).toPlainText(); + } + } else { + Q_EMIT error(this); + return; + } + + int pos = expMatch.capturedStart() + pattern.length(); + const QString sub = data.mid(pos - 4, pattern.length() + 10); + m_remoteUrl = QUrl(QStringLiteral("https://epod.usra.edu/.a/%1-pi").arg(sub)); + + KIO::StoredTransferJob *imageJob = KIO::storedGet(m_remoteUrl, KIO::NoReload, KIO::HideProgressInfo); + connect(imageJob, &KIO::StoredTransferJob::finished, this, &EpodProvider::imageRequestFinished); +} + +void EpodProvider::imageRequestFinished(KJob *_job) +{ + KIO::StoredTransferJob *job = static_cast(_job); + if (job->error()) { + Q_EMIT error(this); + return; + } + + // FIXME: this really should be done in a thread as this can block + Q_EMIT finished(this, QImage::fromData(job->data())); +} + +K_PLUGIN_CLASS_WITH_JSON(EpodProvider, "epodprovider.json") + +#include "epodprovider.moc" diff --git a/wallpapers/potd/plugins/providers/epodprovider.h b/wallpapers/potd/plugins/providers/epodprovider.h new file mode 100644 index 00000000..3e694a37 --- /dev/null +++ b/wallpapers/potd/plugins/providers/epodprovider.h @@ -0,0 +1,29 @@ +/* + * SPDX-FileCopyrightText: 2007 Tobias Koenig + * SPDX-FileCopyrightText: 2008 Anne-Marie Mahfouf + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#pragma once + +#include "potdprovider.h" + +class KJob; + +/** + * This class provides the image for EPOD + * "Earth Science Picture Of the Day" + * located at https://epod.usra.edu/. + */ +class EpodProvider : public PotdProvider +{ + Q_OBJECT + +public: + explicit EpodProvider(QObject *parent, const KPluginMetaData &data, const QVariantList &args); + +private: + void pageRequestFinished(KJob *job); + void imageRequestFinished(KJob *job); +}; diff --git a/wallpapers/potd/plugins/providers/epodprovider.json b/wallpapers/potd/plugins/providers/epodprovider.json new file mode 100644 index 00000000..768d4ada --- /dev/null +++ b/wallpapers/potd/plugins/providers/epodprovider.json @@ -0,0 +1,50 @@ +{ + "KPlugin": { + "Icon": "", + "Name": "Earth Science (USRA)", + "Name[ar]": "علوم الأرض (USRA)", + "Name[az]": "Planetimizi tanıyaq (USRA)", + "Name[bg]": "Наука за земята (USRA)", + "Name[ca@valencia]": "Ciències de la Terra (USRA)", + "Name[ca]": "Ciències de la Terra (USRA)", + "Name[cs]": "Výzkum Země (USRA)", + "Name[da]": "Earth Science (USRA)", + "Name[de]": "Earth Science (USRA)", + "Name[en_GB]": "Earth Science (USRA)", + "Name[eo]": "Terscienco (USRA)", + "Name[es]": "Ciencias de la Tierra (USRA)", + "Name[et]": "Earth Science (USRA)", + "Name[eu]": "Lurreko zientzia - Earth Science (USRA)", + "Name[fi]": "Earth Science (USRA)", + "Name[fr]": "Sciences de la terre (USRA)", + "Name[gl]": "Ciencias da terra (USRA)", + "Name[he]": "מדעי כדור הארץ (USRA)", + "Name[hu]": "Földtudomány (USRA)", + "Name[ia]": "Earth Science (USRA)", + "Name[id]": "Earth Science (USRA)", + "Name[is]": "Jarðvísindi (USRA)", + "Name[it]": "Scienza della Terra (USRA)", + "Name[ja]": "地球科学 (USRA)", + "Name[ka]": "დედამიწის მეცნიერებები (USRA)", + "Name[ko]": "지구과학(USRA)", + "Name[lt]": "Mokslas apie Žemę (USRA)", + "Name[lv]": "Zemes zinātnes (USRA)", + "Name[nl]": "Aardwetenschappen (USRA)", + "Name[nn]": "Geovitskap (USRA)", + "Name[pl]": "Nauka o Ziemi (USRA)", + "Name[pt]": "Earth Science (USRA)", + "Name[pt_BR]": "Earth Science (USRA)", + "Name[ro]": "Științele Pământului (USRA)", + "Name[ru]": "Изучение земли (USRA)", + "Name[sk]": "Veda o zemi (USRA)", + "Name[sl]": "Earth Science (USRA)", + "Name[sv]": "Geovetenskap (USRA)", + "Name[tr]": "Earth Science (USRA)", + "Name[uk]": "Earth Science (USRA)", + "Name[vi]": "Khoa học Trái Đất (USRA)", + "Name[x-test]": "xxEarth Science (USRA)xx", + "Name[zh_CN]": "地球科学 (USRA)", + "Name[zh_TW]": "地球科學 (USRA)" + }, + "X-KDE-PlasmaPoTDProvider-Identifier": "epod" +} diff --git a/wallpapers/potd/plugins/providers/flickrprovider.cpp b/wallpapers/potd/plugins/providers/flickrprovider.cpp new file mode 100644 index 00000000..21efccd5 --- /dev/null +++ b/wallpapers/potd/plugins/providers/flickrprovider.cpp @@ -0,0 +1,269 @@ +// SPDX-FileCopyrightText: 2007 Tobias Koenig +// SPDX-FileCopyrightText: 2008 Anne-Marie Mahfouf +// SPDX-FileCopyrightText: 2008 Georges Toth +// SPDX-FileCopyrightText: 2021 Guo Yunhe +// +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "flickrprovider.h" + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "debug.h" + +using namespace Qt::StringLiterals; + +static QUrl buildUrl(const QDate &date, const QString &apiKey) +{ + QUrl url(QLatin1String("https://api.flickr.com/services/rest/")); + QUrlQuery urlQuery(url); + urlQuery.addQueryItem(QStringLiteral("api_key"), apiKey); + urlQuery.addQueryItem(QStringLiteral("method"), QStringLiteral("flickr.interestingness.getList")); + urlQuery.addQueryItem(QStringLiteral("date"), date.toString(Qt::ISODate)); + // url_o might be either too small or too large. + urlQuery.addQueryItem(QStringLiteral("extras"), QStringLiteral("url_k,url_h,url_o")); + url.setQuery(urlQuery); + + return url; +} + +FlickrProvider::FlickrProvider(QObject *parent, const KPluginMetaData &data, const QVariantList &args) + : PotdProvider(parent, data, args) +{ + connect(this, &FlickrProvider::configLoaded, this, &FlickrProvider::sendXmlRequest); + + loadConfig(); +} + +void FlickrProvider::configRequestFinished(KJob *_job) +{ + KIO::StoredTransferJob *job = static_cast(_job); + if (job->error()) { + qCWarning(WALLPAPERPOTD) << "configRequestFinished error: failed to fetch data"; + Q_EMIT error(this); + return; + } + + KIO::StoredTransferJob *putJob = KIO::storedPut(job->data(), m_configLocalUrl, -1); + connect(putJob, &KIO::StoredTransferJob::finished, this, &FlickrProvider::configWriteFinished); +} + +void FlickrProvider::configWriteFinished(KJob *_job) +{ + KIO::StoredTransferJob *job = static_cast(_job); + if (job->error()) { + qCWarning(WALLPAPERPOTD) << "configWriteFinished error: failed to write data." << job->errorText(); + Q_EMIT error(this); + } else { + loadConfig(); + } +} + +void FlickrProvider::loadConfig() +{ + // TODO move to flickr provider + const QString configFileName = QStringLiteral("%1provider.conf").arg(identifier()); + m_configRemoteUrl = QUrl(QStringLiteral("https://autoconfig.kde.org/potd/") + configFileName); + m_configLocalPath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/plasma_engine_potd/") + configFileName; + m_configLocalUrl = QUrl::fromLocalFile(m_configLocalPath); + + auto config = KSharedConfig::openConfig(m_configLocalPath, KConfig::NoGlobals); + KConfigGroup apiGroup = config->group("API"); + QString apiKey = apiGroup.readEntry("API_KEY"); + QString apiSecret = apiGroup.readEntry("API_SECRET"); + + Q_EMIT configLoaded(apiKey, apiSecret); +} + +void FlickrProvider::refreshConfig() +{ + // You can only refresh it once in a provider's life cycle + if (m_refreshed) { + return; + } + // You can only refresh it once in a day + QFileInfo configFileInfo = QFileInfo(m_configLocalPath); + if (configFileInfo.exists() && configFileInfo.lastModified().addDays(1) > QDateTime::currentDateTime()) { + return; + } + + KIO::StoredTransferJob *job = KIO::storedGet(m_configRemoteUrl, KIO::NoReload, KIO::HideProgressInfo); + connect(job, &KIO::StoredTransferJob::finished, this, &FlickrProvider::configRequestFinished); + + m_refreshed = true; +} + +void FlickrProvider::sendXmlRequest(const QString &apiKey) +{ + if (apiKey.isNull()) { + refreshConfig(); + return; + } + + mApiKey = apiKey; + mActualDate = QDate::currentDate().addDays(-2); + + const QUrl xmlUrl = buildUrl(mActualDate, apiKey); + + KIO::StoredTransferJob *xmlJob = KIO::storedGet(xmlUrl, KIO::NoReload, KIO::HideProgressInfo); + connect(xmlJob, &KIO::StoredTransferJob::finished, this, &FlickrProvider::xmlRequestFinished); +} + +void FlickrProvider::xmlRequestFinished(KJob *_job) +{ + KIO::StoredTransferJob *job = static_cast(_job); + if (job->error()) { + qCWarning(WALLPAPERPOTD) << "XML request error:" << job->errorText(); + Q_EMIT error(this); + return; + } + + // Clear the list + m_photoList.clear(); + m_photoList.reserve(100); + + xml.clear(); + xml.addData(job->data()); + + while (!xml.atEnd()) { + xml.readNext(); + + if (xml.isStartElement()) { + const auto attributes = xml.attributes(); + if (xml.name() == QLatin1String("rsp")) { + /* no pictures available for the specified parameters */ + if (attributes.value(QLatin1String("stat")) != QLatin1String("ok")) { + qCWarning(WALLPAPERPOTD) << "xmlRequestFinished error: no photos for the query"; + Q_EMIT error(this); + return; + } + } else if (xml.name() == QLatin1String("photo")) { + if (attributes.value(QLatin1String("ispublic")) != QLatin1String("1")) { + continue; + } + + constexpr QLatin1String fallbackList[] = {"url_k"_L1, "url_h"_L1}; + + bool found = false; + for (auto urlAttrString : fallbackList) { + // Get the best url. + if (attributes.hasAttribute(urlAttrString)) { + QString title, userId, photoId; + if (attributes.hasAttribute(QLatin1String("title"))) { + title = QTextDocumentFragment::fromHtml(attributes.value(QLatin1String("title")).toString().trimmed()).toPlainText(); + } + if (attributes.hasAttribute(QLatin1String("owner")) && attributes.hasAttribute(QLatin1String("id"))) { + userId = attributes.value(QLatin1String("owner")).toString(); + photoId = attributes.value(QLatin1String("id")).toString(); + } + m_photoList.emplace_back(PhotoEntry{ + attributes.value(urlAttrString).toString(), + title, + userId, + photoId, + }); + found = true; + break; + } + } + + // The logic here is, if url_h or url_k are present, url_o must + // has higher quality, otherwise, url_o is worse than k/h size. + // If url_o is better, prefer url_o. + if (found) { + constexpr QLatin1String originAttr("url_o"); + if (attributes.hasAttribute(originAttr)) { + m_photoList.back().urlString = attributes.value(originAttr).toString(); + } + } + } + } + } + + if (xml.error() && xml.error() != QXmlStreamReader::PrematureEndOfDocumentError) { + qCWarning(WALLPAPERPOTD) << "XML ERROR at line" << xml.lineNumber() << xml.error(); + } + + if (m_photoList.begin() != m_photoList.end()) { + // Plasma 5.24.0 release date + std::mt19937 randomEngine(QDate(2022, 2, 3).daysTo(QDate::currentDate())); + std::uniform_int_distribution distrib(0, m_photoList.size() - 1); + + const PhotoEntry &randomPhotoEntry = m_photoList.at(distrib(randomEngine)); + m_remoteUrl = QUrl(randomPhotoEntry.urlString); + m_title = randomPhotoEntry.title; + + /** + * Visit the photo page to get the author + * API document: https://www.flickr.com/services/api/misc.urls.html + * https://www.flickr.com/photos/{user-id}/{photo-id} + */ + if (!(randomPhotoEntry.userId.isEmpty() || randomPhotoEntry.photoId.isEmpty())) { + m_infoUrl = QUrl(QStringLiteral("https://www.flickr.com/photos/%1/%2").arg(randomPhotoEntry.userId, randomPhotoEntry.photoId)); + } + + KIO::StoredTransferJob *imageJob = KIO::storedGet(m_remoteUrl, KIO::NoReload, KIO::HideProgressInfo); + connect(imageJob, &KIO::StoredTransferJob::finished, this, &FlickrProvider::imageRequestFinished); + } else { + qCWarning(WALLPAPERPOTD) << "List is empty in XML file"; + Q_EMIT error(this); + } +} + +void FlickrProvider::imageRequestFinished(KJob *_job) +{ + KIO::StoredTransferJob *job = static_cast(_job); + if (job->error()) { + qCWarning(WALLPAPERPOTD) << "Image request error:" << job->errorText(); + Q_EMIT error(this); + return; + } + + m_image = QImage::fromData(job->data()); + + // Visit the photo page to get the author + if (!m_infoUrl.isEmpty()) { + KIO::StoredTransferJob *pageJob = KIO::storedGet(m_infoUrl, KIO::NoReload, KIO::HideProgressInfo); + connect(pageJob, &KIO::StoredTransferJob::finished, this, &FlickrProvider::pageRequestFinished); + } else { + // No information is fine + Q_EMIT finished(this, m_image); + } +} + +void FlickrProvider::pageRequestFinished(KJob *_job) +{ + KIO::StoredTransferJob *job = static_cast(_job); + if (job->error()) { + qCWarning(WALLPAPERPOTD) << "No author available"; + Q_EMIT finished(this, m_image); + return; + } + + const QString data = QString::fromUtf8(job->data()).simplified(); + + // Example: Hammerchewer + QRegularExpression authorRegEx(QStringLiteral("(.+?)")); + QRegularExpressionMatch match = authorRegEx.match(data); + + if (match.hasMatch()) { + m_author = QTextDocumentFragment::fromHtml(match.captured(1).trimmed()).toPlainText(); + } + + Q_EMIT finished(this, m_image); +} + +K_PLUGIN_CLASS_WITH_JSON(FlickrProvider, "flickrprovider.json") + +#include "flickrprovider.moc" diff --git a/wallpapers/potd/plugins/providers/flickrprovider.h b/wallpapers/potd/plugins/providers/flickrprovider.h new file mode 100644 index 00000000..82a96f6e --- /dev/null +++ b/wallpapers/potd/plugins/providers/flickrprovider.h @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: 2007 Tobias Koenig +// SPDX-FileCopyrightText: 2008 Anne-Marie Mahfouf +// SPDX-FileCopyrightText: 2008 Georges Toth +// SPDX-FileCopyrightText: 2021 Guo Yunhe +// +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "potdprovider.h" + +#include +#include + +#include + +/** + * This class grabs a random image from the flickr + * interestingness stream of pictures, for the given date. + * Should there be no image for the current date, it tries + * to grab one from the day before yesterday. + */ +class FlickrProvider : public PotdProvider +{ + Q_OBJECT + +public: + explicit FlickrProvider(QObject *parent, const KPluginMetaData &data, const QVariantList &args); + +Q_SIGNALS: + void configLoaded(const QString &apiKey, const QString &apiSecret); + +private Q_SLOTS: + void configRequestFinished(KJob *job); + void configWriteFinished(KJob *job); + +private: + void loadConfig(); + void refreshConfig(); + + void sendXmlRequest(const QString &apiKey); + void xmlRequestFinished(KJob *job); + void imageRequestFinished(KJob *job); + + /** + * Parse the author from the HTML source + */ + void pageRequestFinished(KJob *job); + +private: + QString m_configLocalPath; + QUrl m_configRemoteUrl; + QUrl m_configLocalUrl; + QDate mActualDate; + QString mApiKey; + bool m_refreshed = false; + + QImage m_image; + + QXmlStreamReader xml; + + int mFailureNumber = 0; + + struct PhotoEntry { + QString urlString; + QString title; + QString userId; + QString photoId; + }; + std::vector m_photoList; +}; diff --git a/wallpapers/potd/plugins/providers/flickrprovider.json b/wallpapers/potd/plugins/providers/flickrprovider.json new file mode 100644 index 00000000..58215517 --- /dev/null +++ b/wallpapers/potd/plugins/providers/flickrprovider.json @@ -0,0 +1,51 @@ +{ + "KPlugin": { + "Icon": "", + "Name": "Flickr", + "Name[ar]": "فليكر", + "Name[ast]": "Flickr", + "Name[az]": "Flickr", + "Name[bg]": "Flickr", + "Name[ca@valencia]": "Flickr", + "Name[ca]": "Flickr", + "Name[cs]": "Flickr", + "Name[da]": "Flickr", + "Name[de]": "Flickr", + "Name[en_GB]": "Flickr", + "Name[eo]": "Flickr", + "Name[es]": "Flickr", + "Name[et]": "Flickr", + "Name[eu]": "Flickr", + "Name[fi]": "Flickr", + "Name[fr]": "Flickr", + "Name[gl]": "Flickr", + "Name[he]": "Flickr", + "Name[hu]": "Flickr", + "Name[ia]": "Flickr", + "Name[id]": "Flickr", + "Name[is]": "Flickr", + "Name[it]": "Flickr", + "Name[ja]": "Flickr", + "Name[ka]": "Flickr", + "Name[ko]": "Flickr", + "Name[lt]": "Flickr", + "Name[lv]": "„Flickr“", + "Name[nl]": "Flickr", + "Name[nn]": "Flickr", + "Name[pl]": "Flickr", + "Name[pt]": "Flickr", + "Name[pt_BR]": "Flickr", + "Name[ro]": "Flickr", + "Name[ru]": "Flickr", + "Name[sk]": "Flickr", + "Name[sl]": "Flickr", + "Name[sv]": "Flickr", + "Name[tr]": "Flickr", + "Name[uk]": "Flickr", + "Name[vi]": "Flickr", + "Name[x-test]": "xxFlickrxx", + "Name[zh_CN]": "Flickr", + "Name[zh_TW]": "Flickr" + }, + "X-KDE-PlasmaPoTDProvider-Identifier": "flickr" +} diff --git a/wallpapers/potd/plugins/providers/noaaprovider.cpp b/wallpapers/potd/plugins/providers/noaaprovider.cpp new file mode 100644 index 00000000..e840fa0e --- /dev/null +++ b/wallpapers/potd/plugins/providers/noaaprovider.cpp @@ -0,0 +1,114 @@ +/* + * SPDX-FileCopyrightText: 2007 Tobias Koenig + * SPDX-FileCopyrightText: 2008 Anne-Marie Mahfouf + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "noaaprovider.h" + +#include +#include // For parsing title from HTML source + +#include +#include + +#include "debug.h" + +NOAAProvider::NOAAProvider(QObject *parent, const KPluginMetaData &data, const QVariantList &args) + : PotdProvider(parent, data, args) +{ + const QUrl url(QStringLiteral("https://www.nesdis.noaa.gov/real-time-imagery/imagery-collections/image-of-the-day")); + + KIO::StoredTransferJob *job = KIO::storedGet(url, KIO::NoReload, KIO::HideProgressInfo); + connect(job, &KIO::StoredTransferJob::finished, this, &NOAAProvider::listPageRequestFinished); +} + +void NOAAProvider::listPageRequestFinished(KJob *_job) +{ + KIO::StoredTransferJob *job = static_cast(_job); + if (job->error()) { + Q_EMIT error(this); + return; + } + + const QString data = QString::fromUtf8(job->data()).simplified(); + + // Using regular expression could be fragile in such case, but the HTML + // NOAA page itself is not a valid XML file and unfortunately it could + // not be parsed successfully till the content we want. And we do not want + // to use heavy weight QtWebkit. So we use QRegularExpression to capture + // the wanted url here. + // Example: Hunga Tonga-Hunga Ha'apai Erupts Again + const QRegularExpression re("
.*?
  • .*?"); + auto result = re.match(data); + if (result.hasMatch()) { + m_infoUrl = QUrl(QStringLiteral("https://www.nesdis.noaa.gov") + result.captured(1)); + } + if (!m_infoUrl.isValid()) { + qCWarning(WALLPAPERPOTD) << "Failed to get the latest article from NOAAProvider!"; + Q_EMIT error(this); + return; + } + + KIO::StoredTransferJob *pageJob = KIO::storedGet(m_infoUrl, KIO::NoReload, KIO::HideProgressInfo); + connect(pageJob, &KIO::StoredTransferJob::finished, this, &NOAAProvider::pageRequestFinished); +} + +void NOAAProvider::pageRequestFinished(KJob *_job) +{ + KIO::StoredTransferJob *job = static_cast(_job); + if (job->error()) { + Q_EMIT error(this); + return; + } + + const QString data = QString::fromUtf8(job->data()).simplified(); + + /** + * Example: + * + * Download Animation + * + */ + const QRegularExpression re(".*?Download.*?"); + const QRegularExpressionMatch result = re.match(data); + if (result.hasMatch()) { + m_remoteUrl = QUrl(QStringLiteral("https://www.nesdis.noaa.gov") + result.captured(1)); + } + if (!m_remoteUrl.isValid()) { + qWarning() << "Failed to match the latest image URL from NOAAProvider!"; + Q_EMIT error(this); + return; + } + + /** + * Match title + * Example: + * + */ + const QRegularExpression titleRegEx(QStringLiteral("(_job); + if (job->error()) { + qCWarning(WALLPAPERPOTD) << "Failed to get the latest image from NOAAProvider. Please report the issue on bugs.kde.org"; + Q_EMIT error(this); + return; + } + + Q_EMIT finished(this, QImage::fromData(job->data())); +} + +K_PLUGIN_CLASS_WITH_JSON(NOAAProvider, "noaaprovider.json") + +#include "noaaprovider.moc" diff --git a/wallpapers/potd/plugins/providers/noaaprovider.h b/wallpapers/potd/plugins/providers/noaaprovider.h new file mode 100644 index 00000000..086b8c87 --- /dev/null +++ b/wallpapers/potd/plugins/providers/noaaprovider.h @@ -0,0 +1,31 @@ +/* + * SPDX-FileCopyrightText: 2007 Tobias Koenig + * SPDX-FileCopyrightText: 2008 Anne-Marie Mahfouf + * SPDX-FileCopyrightText: 2016 Weng Xuetian + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#pragma once + +#include "potdprovider.h" + +class KJob; + +/** + * This class provides the image for NOAA Environmental Visualization Laboratory + * Image Of the Day + * located at https://www.nesdis.noaa.gov/content/imagery-and-data. + */ +class NOAAProvider : public PotdProvider +{ + Q_OBJECT + +public: + explicit NOAAProvider(QObject *parent, const KPluginMetaData &data, const QVariantList &args); + +private: + void listPageRequestFinished(KJob *job); + void pageRequestFinished(KJob *job); + void imageRequestFinished(KJob *job); +}; diff --git a/wallpapers/potd/plugins/providers/noaaprovider.json b/wallpapers/potd/plugins/providers/noaaprovider.json new file mode 100644 index 00000000..f3671247 --- /dev/null +++ b/wallpapers/potd/plugins/providers/noaaprovider.json @@ -0,0 +1,50 @@ +{ + "KPlugin": { + "Icon": "", + "Name": "Weather Satellite (NOAA)", + "Name[ar]": "قمر الطقس (NOAA)", + "Name[az]": "Metereoloji Peyk (NOAA)", + "Name[bg]": "Метеорологичен сателит (NOAA)", + "Name[ca@valencia]": "Satèl·lits meteorològics (NOAA)", + "Name[ca]": "Satèl·lits meteorològics (NOAA)", + "Name[cs]": "Meteorologický satelit (NOAA)", + "Name[da]": "Vejrsatellit (NOAA)", + "Name[de]": "Wettersatellit (NOAA)", + "Name[en_GB]": "Weather Satellite (NOAA)", + "Name[eo]": "Vetera Satelito (NOAA)", + "Name[es]": "Satélite meteorológico (NOAA)", + "Name[et]": "Ilmasatelliit (NOAA)", + "Name[eu]": "Satelite meteorologikoa - Weather Satellite (NOAA)", + "Name[fi]": "Sääsatelliitti (NOAA)", + "Name[fr]": "Satellite météo (NOAA)", + "Name[gl]": "Satélite meteorolóxico (NOAA)", + "Name[he]": "לוויין מזג אוויר (NOAA)", + "Name[hu]": "Meteorológiai műhold (NOAA)", + "Name[ia]": "Weather Satellite (NOAA)", + "Name[id]": "Weather Satellite (NOAA)", + "Name[is]": "Veðurtungl (NOAA)", + "Name[it]": "Satellite meteo (NOAA)", + "Name[ja]": "気象衛星 (NOAA)", + "Name[ka]": "ამინდის თანამგზავრი (NOAA)", + "Name[ko]": "기상 위성(NOAA)", + "Name[lt]": "Orų palydovas (NOAA)", + "Name[lv]": "Laikapstākļu satelÄ«ts (NOAA)", + "Name[nl]": "Weersatelliet (NOAA)", + "Name[nn]": "Vêrsatellitt (NOAA)", + "Name[pl]": "Satelita pogodowa (NOAA)", + "Name[pt]": "Weather Satellite (NOAA)", + "Name[pt_BR]": "Satélite meteorológico (NOAA)", + "Name[ro]": "Satelit meteorologic (NOAA)", + "Name[ru]": "Метеоспутник (NOAA)", + "Name[sk]": "Meteorologický satelit (NOAA)", + "Name[sl]": "Vremenski satelit (NOAA)", + "Name[sv]": "Vädersatellit (NOAA)", + "Name[tr]": "Hava Durumu Uydusu (NOAA)", + "Name[uk]": "Метеорологічний супутник (NOAA)", + "Name[vi]": "Vệ tinh thời tiết (NOAA)", + "Name[x-test]": "xxWeather Satellite (NOAA)xx", + "Name[zh_CN]": "气象卫星 (NOAA)", + "Name[zh_TW]": "氣象衛星 (NOAA)" + }, + "X-KDE-PlasmaPoTDProvider-Identifier": "noaa" +} diff --git a/wallpapers/potd/plugins/providers/pixabayprovider.conf b/wallpapers/potd/plugins/providers/pixabayprovider.conf new file mode 100644 index 00000000..c622fcfb --- /dev/null +++ b/wallpapers/potd/plugins/providers/pixabayprovider.conf @@ -0,0 +1,2 @@ +[API] +API_KEY=18987723-c268f0dc4e02ed821b64dc7c1 \ No newline at end of file diff --git a/wallpapers/potd/plugins/providers/simonstalenhagprovider.cpp b/wallpapers/potd/plugins/providers/simonstalenhagprovider.cpp new file mode 100644 index 00000000..4730d95e --- /dev/null +++ b/wallpapers/potd/plugins/providers/simonstalenhagprovider.cpp @@ -0,0 +1,111 @@ +/* + * SPDX-FileCopyrightText: 2021 Alexey Andreyev + * + * SPDX-License-Identifier: LicenseRef-KDE-Accepted-GPL + */ + +#include "simonstalenhagprovider.h" + +#include + +#include +#include + +#include +#include + +static QJsonValue randomArrayValueByKey(const QJsonObject &object, QLatin1String key) +{ + QJsonValue result; + + if (object.isEmpty()) { + return result; + } + + auto array = object.value(key).toArray(); + + if (array.isEmpty()) { + return result; + } + + // Plasma 5.24.0 release date + std::mt19937 randomEngine(QDate(2022, 2, 3).daysTo(QDate::currentDate())); + std::uniform_int_distribution distrib(0, array.size() - 1); + + return array.at(distrib(randomEngine)); +} + +SimonStalenhagProvider::SimonStalenhagProvider(QObject *parent, const KPluginMetaData &data, const QVariantList &args) + : PotdProvider(parent, data, args) +{ + const QUrl url(QStringLiteral("https://raw.githubusercontent.com/a-andreyev/simonstalenhag-se-metadata/main/entrypoint.json")); + + KIO::StoredTransferJob *job = KIO::storedGet(url, KIO::NoReload, KIO::HideProgressInfo); + connect(job, &KIO::StoredTransferJob::finished, this, &SimonStalenhagProvider::entrypointRequestFinished); +} + +void SimonStalenhagProvider::entrypointRequestFinished(KJob *_job) +{ + KIO::StoredTransferJob *job = static_cast(_job); + if (job->error()) { + Q_EMIT error(this); + return; + } + + auto json = QJsonDocument::fromJson(job->data()); + auto metadataString = randomArrayValueByKey(json.object(), QLatin1String("simonstalenhag-se-entrypoint")); + auto urlStr = metadataString.toString(); + if (urlStr.isEmpty()) { + Q_EMIT error(this); + return; + } + QUrl metaDataUrl(urlStr); + KIO::StoredTransferJob *metaDataJob = KIO::storedGet(metaDataUrl, KIO::NoReload, KIO::HideProgressInfo); + connect(metaDataJob, &KIO::StoredTransferJob::finished, this, &SimonStalenhagProvider::metaDataRequestFinished); +} + +void SimonStalenhagProvider::metaDataRequestFinished(KJob *_job) +{ + KIO::StoredTransferJob *job = static_cast(_job); + if (job->error()) { + Q_EMIT error(this); + return; + } + + auto json = QJsonDocument::fromJson(job->data()); + auto imageObj = randomArrayValueByKey(json.object(), QLatin1String("simonstalenhag.se")); + auto urlStr = imageObj.toObject().value(QLatin1String("imagebig")).toString(); + if (urlStr.isEmpty()) { + Q_EMIT error(this); + return; + } + m_remoteUrl = QUrl(urlStr); + + const QString titleStr = imageObj.toObject().value(QStringLiteral("name")).toString(); + const QString sectionStr = imageObj.toObject().value(QStringLiteral("section")).toString(); + if (!titleStr.isEmpty()) { + if (!sectionStr.isEmpty()) { + m_title = sectionStr + " - " + titleStr; + } else { + m_title = titleStr; + } + } + + KIO::StoredTransferJob *imageJob = KIO::storedGet(m_remoteUrl, KIO::NoReload, KIO::HideProgressInfo); + connect(imageJob, &KIO::StoredTransferJob::finished, this, &SimonStalenhagProvider::imageRequestFinished); +} + +void SimonStalenhagProvider::imageRequestFinished(KJob *_job) +{ + KIO::StoredTransferJob *job = static_cast(_job); + if (job->error()) { + Q_EMIT error(this); + return; + } + QByteArray data = job->data(); + Q_EMIT finished(this, QImage::fromData(data)); +} + +K_PLUGIN_CLASS_WITH_JSON(SimonStalenhagProvider, "simonstalenhagprovider.json") + +#include "simonstalenhagprovider.moc" diff --git a/wallpapers/potd/plugins/providers/simonstalenhagprovider.h b/wallpapers/potd/plugins/providers/simonstalenhagprovider.h new file mode 100644 index 00000000..f6c0cb57 --- /dev/null +++ b/wallpapers/potd/plugins/providers/simonstalenhagprovider.h @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2021 Alexey Andreyev + * + * SPDX-License-Identifier: LicenseRef-KDE-Accepted-GPL + */ + +#ifndef UNSPLASHPROVIDER_H +#define UNSPLASHPROVIDER_H + +#include "potdprovider.h" + +class KJob; + +/** + * This class provides random wallpapers from Simon Stalenhag website + * http://simonstalenhag.se/ + * see also: https://github.com/a-andreyev/simonstalenhag-se-metadata + */ +class SimonStalenhagProvider : public PotdProvider +{ + Q_OBJECT + +public: + explicit SimonStalenhagProvider(QObject *parent, const KPluginMetaData &data, const QVariantList &args); + +private: + void entrypointRequestFinished(KJob *job); + void metaDataRequestFinished(KJob *job); + void imageRequestFinished(KJob *job); +}; + +#endif diff --git a/wallpapers/potd/plugins/providers/simonstalenhagprovider.json b/wallpapers/potd/plugins/providers/simonstalenhagprovider.json new file mode 100644 index 00000000..094d6fe1 --- /dev/null +++ b/wallpapers/potd/plugins/providers/simonstalenhagprovider.json @@ -0,0 +1,49 @@ +{ + "KPlugin": { + "Icon": "", + "Name": "Simon StÃ¥lenhag Wallpapers", + "Name[ar]": "خلفيات سيمون ستالينهاغ", + "Name[az]": "Simon StÃ¥lenhag'ın divar kağızları", + "Name[bg]": "Тапети от Simon StÃ¥lenhag", + "Name[ca@valencia]": "Fons de pantalla d'en Simon StÃ¥lenhag", + "Name[ca]": "Fons de pantalla d'en Simon StÃ¥lenhag", + "Name[cs]": "Tapety Simona StÃ¥lenhaga", + "Name[da]": "Simon StÃ¥lenhag baggrunde", + "Name[de]": "Hintergrundbilder von Simon StÃ¥lenhag", + "Name[en_GB]": "Simon StÃ¥lenhag Wallpapers", + "Name[eo]": "Simon StÃ¥lenhag Tapetoj", + "Name[es]": "Fondos de pantalla de Simon StÃ¥lenhag", + "Name[eu]": "Simon StÃ¥lenhag Horma-paperak", + "Name[fi]": "Simon StÃ¥lenhagin taustakuvat", + "Name[fr]": "Fonds d'écran de Simon StÃ¥lenhag ", + "Name[gl]": "Fondos de Simon StÃ¥lenhag", + "Name[he]": "טפטים של סיימון סטולנהאנג", + "Name[hu]": "Simon StÃ¥lenhag háttérképek", + "Name[ia]": "Simon StÃ¥lenhag Wallpapers (tapetes de papiro)", + "Name[id]": "Wallpaper dari Simon StÃ¥lenhag", + "Name[is]": "Veggfóður frá Simon StÃ¥lenhag", + "Name[it]": "Sfondi di Simon StÃ¥lenhag", + "Name[ja]": "Simon StÃ¥lenhag 壁紙", + "Name[ka]": "Simon StÃ¥lenhag -ის ფონის სურათები", + "Name[ko]": "Simon StÃ¥lenhag ë°°ê²½ 그림", + "Name[lt]": "Simon StÃ¥lenhag darbalaukio fonai", + "Name[lv]": "Simon StÃ¥lenhag ekrāntapetes", + "Name[nl]": "Achtergrondafbeeldingen Simon StÃ¥lenhag", + "Name[nn]": "Simon StÃ¥lenhags bakgrunnsbilete", + "Name[pl]": "Tapety Simona StÃ¥lenhaga", + "Name[pt]": "Papéis de Parede de Simon StÃ¥lenhag", + "Name[pt_BR]": "Papéis de parede do Simon StÃ¥lenhag", + "Name[ro]": "Tapete de Simon StÃ¥lenhag", + "Name[ru]": "Обои от Simon StÃ¥lenhag", + "Name[sk]": "Tapety od Simon StÃ¥lenhag", + "Name[sl]": "Simon StÃ¥lenhag Wallpapers", + "Name[sv]": "Skrivbordsunderlägg av Simon StÃ¥lenhag", + "Name[tr]": "Simon StÃ¥lenhag Duvar Kağıtları", + "Name[uk]": "Фонові зображення Сімона Стелегаґа (Simon StÃ¥lenhag)", + "Name[vi]": "Phông nền Simon StÃ¥lenhag", + "Name[x-test]": "xxSimon StÃ¥lenhag Wallpapersxx", + "Name[zh_CN]": "Simon StÃ¥lenhag 壁纸", + "Name[zh_TW]": "西蒙·斯塔倫海格 (Simon StÃ¥lenhag) 桌布" + }, + "X-KDE-PlasmaPoTDProvider-Identifier": "simonstalenhag" +} diff --git a/wallpapers/potd/plugins/providers/wcpotdprovider.cpp b/wallpapers/potd/plugins/providers/wcpotdprovider.cpp new file mode 100644 index 00000000..2e8f3183 --- /dev/null +++ b/wallpapers/potd/plugins/providers/wcpotdprovider.cpp @@ -0,0 +1,95 @@ +/* + * SPDX-FileCopyrightText: 2007 Tobias Koenig + * SPDX-FileCopyrightText: 2008 Anne-Marie Mahfouf + * SPDX-FileCopyrightText: 2016 Weng Xuetian + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "wcpotdprovider.h" + +#include +#include +#include +#include + +#include +#include + +WcpotdProvider::WcpotdProvider(QObject *parent, const KPluginMetaData &data, const QVariantList &args) + : PotdProvider(parent, data, args) +{ + QUrl url(QStringLiteral("https://commons.wikimedia.org/w/api.php")); + + QUrlQuery urlQuery(url); + urlQuery.addQueryItem(QStringLiteral("action"), QStringLiteral("parse")); + urlQuery.addQueryItem(QStringLiteral("text"), QStringLiteral("{{Potd}}")); + urlQuery.addQueryItem(QStringLiteral("contentmodel"), QStringLiteral("wikitext")); + urlQuery.addQueryItem(QStringLiteral("format"), QStringLiteral("json")); + url.setQuery(urlQuery); + + KIO::StoredTransferJob *job = KIO::storedGet(url, KIO::NoReload, KIO::HideProgressInfo); + connect(job, &KIO::StoredTransferJob::finished, this, &WcpotdProvider::pageRequestFinished); +} + +void WcpotdProvider::pageRequestFinished(KJob *_job) +{ + KIO::StoredTransferJob *job = static_cast(_job); + if (job->error()) { + Q_EMIT error(this); + return; + } + + const QJsonObject jsonObject = QJsonDocument::fromJson(job->data()).object().value(QLatin1String("parse")).toObject(); + const QJsonArray jsonImageArray = jsonObject.value(QLatin1String("images")).toArray(); + + if (jsonImageArray.size() == 0) { + Q_EMIT error(this); + return; + } + + const QString imageFile = jsonImageArray.at(0).toString(); + if (!imageFile.isEmpty()) { + m_remoteUrl = QUrl(QStringLiteral("https://commons.wikimedia.org/wiki/Special:FilePath/") + imageFile); + KIO::StoredTransferJob *imageJob = KIO::storedGet(m_remoteUrl, KIO::NoReload, KIO::HideProgressInfo); + connect(imageJob, &KIO::StoredTransferJob::finished, this, &WcpotdProvider::imageRequestFinished); + } else { + Q_EMIT error(this); + return; + } + + const QJsonObject jsonTextObject = jsonObject.value(QStringLiteral("text")).toObject(); + const QString text = jsonTextObject.value(QStringLiteral("*")).toString().trimmed(); + + if (text.isEmpty()) { + return; + } + + /** + * Match link and title. + * Example: + *
    Mount Petros + */ + const QRegularExpression titleRegEx(QStringLiteral(".*?(.+?)")); + const QRegularExpressionMatch titleMatch = titleRegEx.match(text); + if (titleMatch.hasMatch()) { + m_infoUrl = QUrl(titleMatch.captured(1).trimmed()); + m_title = QTextDocumentFragment::fromHtml(titleMatch.captured(2).trimmed()).toPlainText(); + } +} + +void WcpotdProvider::imageRequestFinished(KJob *_job) +{ + KIO::StoredTransferJob *job = static_cast(_job); + if (job->error()) { + Q_EMIT error(this); + return; + } + QByteArray data = job->data(); + Q_EMIT finished(this, QImage::fromData(data)); +} + +K_PLUGIN_CLASS_WITH_JSON(WcpotdProvider, "wcpotdprovider.json") + +#include "wcpotdprovider.moc" diff --git a/wallpapers/potd/plugins/providers/wcpotdprovider.h b/wallpapers/potd/plugins/providers/wcpotdprovider.h new file mode 100644 index 00000000..69f9e2b4 --- /dev/null +++ b/wallpapers/potd/plugins/providers/wcpotdprovider.h @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2007 Tobias Koenig + * SPDX-FileCopyrightText: 2008 Anne-Marie Mahfouf + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#pragma once + +#include "potdprovider.h" + +class KJob; + +/** + * This class provides the image for the "Wikimedia + * Commons Picture Of the Day" + * located at http://tools.wikimedia.de/~daniel/potd/commons/potd-800x600.html. + * From there extract the picture. + * Using 800x600 as the best size for now, others are available, see + * http://tools.wikimedia.de/~daniel/potd/potd.php + */ +class WcpotdProvider : public PotdProvider +{ + Q_OBJECT + +public: + explicit WcpotdProvider(QObject *parent, const KPluginMetaData &data, const QVariantList &args); + +private: + void pageRequestFinished(KJob *job); + void imageRequestFinished(KJob *job); +}; diff --git a/wallpapers/potd/plugins/providers/wcpotdprovider.json b/wallpapers/potd/plugins/providers/wcpotdprovider.json new file mode 100644 index 00000000..2e0dcad7 --- /dev/null +++ b/wallpapers/potd/plugins/providers/wcpotdprovider.json @@ -0,0 +1,51 @@ +{ + "KPlugin": { + "Icon": "", + "Name": "Wikimedia Commons", + "Name[ar]": "ويكيميديا كومنز ", + "Name[az]": "Wikimedia Commons", + "Name[bg]": "Wikimedia Commons", + "Name[ca@valencia]": "Wikimedia Commons", + "Name[ca]": "Wikimedia Commons", + "Name[cs]": "Wikimedia Commons", + "Name[da]": "Wikimedia Commons", + "Name[de]": "Wikimedia Commons", + "Name[en_GB]": "Wikimedia Commons", + "Name[eo]": "Vikimedia Komunejo", + "Name[es]": "Wikimedia Commons", + "Name[et]": "Wikimedia Commons", + "Name[eu]": "Wikimedia Commons", + "Name[fi]": "Wikimedia Commons", + "Name[fr]": "Wikimedia Commons", + "Name[gl]": "Wikimedia Commons", + "Name[he]": "ויקישיתוף", + "Name[hu]": "Wikimedia Commons", + "Name[ia]": "Wikimedia Commons", + "Name[id]": "Wikimedia Commons", + "Name[is]": "Wikimedia Commons", + "Name[it]": "Wikimedia Commons", + "Name[ja]": "ウィキメディア・コモンズ", + "Name[ka]": "Wikimedia Commons", + "Name[ko]": "위키미디어 공용", + "Name[lt]": "Wikimedia Commons", + "Name[lv]": "Vikikrātuve", + "Name[nl]": "Wikimedia Commons", + "Name[nn]": "Wikimedia Commons", + "Name[pl]": "Wikimedia Commons", + "Name[pt]": "Wikimedia Commons", + "Name[pt_BR]": "Wikimedia Commons", + "Name[ro]": "Wikimedia Commons", + "Name[ru]": "Wikimedia Commons", + "Name[sk]": "Wikimedia Commons", + "Name[sl]": "Wikimedia Commons", + "Name[sv]": "Wikimedia Commons", + "Name[tr]": "Wikimedia Commons", + "Name[uk]": "ВікіСховище", + "Name[vi]": "Wikimedia Commons", + "Name[x-test]": "xxWikimedia Commonsxx", + "Name[zh_CN]": "维基共享资源", + "Name[zh_TW]": "Wikimedia Commons" + }, + "X-KDE-PlasmaPoTDProvider-Identifier": "wcpotd", + "X-KDE-PlasmaPoTDProvider-NotSafeForWork": true +} -- 2.30.2